Leetcode171-190刷題筆記(非困難題)

我是飛醬發表於2020-12-19

171.Excel表列序號

該題目就有點類似於一個進位為26的一個進位制轉換的題目。

    public int titleToNumber(String s) {
        int ans = 0;
        
        for(int i=0; i<s.length(); i++) {
            ans *= 26;
            ans += s.charAt(i) - 'A' + 1;
        }
        
        return ans;
    }

172.階乘後的零個數

該題目暴力法可以解決,但是題目要求我們使用logn查詢,那說明一定是存在一些情況我們可以進行一些省略。

就比如說我們需要找到階乘計算後零的個數,我們可以通過換個思考方式。

什麼樣的情況會出現零?
那就是類似於 2 * 5 / 4 * 10 這樣的情況,我們也可以發現最基礎的情況就是 2 * 5,那麼我們可以尋找兩個數中一共有多少對 2 * 5,換句話說也就是找兩個數的因子了。

2的因子很好找,因為階乘是 1 * 2 * …n,所以每一個偶數就可以作為因子,而5呢也就是10個裡面有兩個,所以我們可以發現其實5的個數源遠少於2的個數,所以我們其實只需要統計在這個階乘計算中,一共可以分離出多少個5就行了。

知道上面那個思想直接寫程式碼。

    //階乘有多少個 0
    public int trailingZeroes(int n) {
        //構成10的結構 2 * 5 這樣的形式,可以構成一個 10
        int ans = 0;

        //通常 2 的出現數量比 5 多(這裡的數字都是作為因子的存在),所以我們每次 + 5可以提高速度
        for(int i=5; i<=n; i+=5) {
            //階乘構成也可能存在多個 5 (例如25,125),所以針對這種情況我們需要進行多次迴圈處理
            int curr = i;
            while(curr % 5 == 0) { //更夠被5整除
                ans += 1;
                curr /= 5;
            }
        }

        return ans;
    }

173.二叉搜尋樹迭代器

題目要求我們的空間複雜度為O(H),H為樹的高度。

這個空間複雜度的限制,讓我們覺得可能需要使用一個棧,並且棧內最多存放的就是一條數到葉子結點的路徑。

這個思路讓我們想到了之前的二叉樹的中序遍歷的迭代版本。使用的是一個模擬的思想。

我們先將所有左子樹入棧(二叉搜尋樹,左子樹一定小於父節點),出棧的時候我們通過判斷結點是否擁有右子樹,如果存在右子樹,則下一個最小值一定存在於右子樹中(二叉搜尋樹性質,右子樹的結點一定大於父節點,但是一定小於父節點的父節點),所以我們則將右子樹部分按照之前的方法入棧(即入棧左子樹部分)。

//二叉搜尋樹迭代器
class BSTIterator {
    private Stack<TreeNode> stack = new Stack<>();
    
    public BSTIterator(TreeNode root) {
        //左子樹的左節點全部入棧
        
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        
    }

    //返回下一位的最小的數
    public int next() {
        //彈出棧頂
        TreeNode pop = stack.pop();
        //如果有右子樹,則按照初始化方式入棧
        if(pop.right != null) {
            TreeNode root = pop.right;
            
            while(root != null) {
                stack.push(root);
                root = root.left;
            }
        }
        
        return pop.val;
    }

    public boolean hasNext() {
        return stack.isEmpty();
    }
}

179.最大數

該題目就是一個比較兩個數誰應該放在前面的題目。

例如 [5,34],自然是534>345,所以我們可以發現排序方式是字典序排序,但是也有特殊情況。

例如[10,2],雖然是210>102,但是2的字典序 > 10的字典序,所以說不能用簡單的字典序排序。

我們這裡使用的是將兩中組合情況列舉出來進行排序,使用比較器Compator。

需要注意的是,如果第一位為0,那麼說明整個陣列都為0,因為任意非0的數和0組合0一定在後面的,比如[0,1],一定是10>01。

    public String largestNumber(int[] nums) {
        List<String> list = new ArrayList<>();

        for(int num : nums) {
            list.add(num + "");
        }

        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //o1在前面
                String s1 = o1 + o2;
                //o2在前面
                String s2 = o2 + o1;

                // s1(o1在前面)字典序大則說明構成的數也大,說明o1應該放在前面,即小於情況
                return -s1.compareTo(s2);
            }
        });

        StringBuilder stringBuilder = new StringBuilder();

		//如果此時第一位還是0,說明全部都是0
		//因為任意非0數和0組合,0一定在最後面。
        if(list.get(0).equals("0")) {
            return "0";
        }
        for(String s : list) {
            //開頭不允許為 0
            stringBuilder.append(s);
        }
        
        return stringBuilder.toString();
    }

187.重複的DNA序列

題目要求是一個長度為10的DNA片段,並且出現次數 > 1。

所以通過這句話我們可以直接發現這是一個滑動視窗的問題,並且明確告訴了視窗長度為10。

則我們通過Set集合,判斷是否出現重複即可,重複則放入我們需要的ans中,需要注意的是結果集最好使用Set,因為可能出現多次DNA片段,但我們只需要新增一次即可。

    //重複的DNA
    public List<String> findRepeatedDnaSequences(String s) {
        Set<String> ans = new HashSet<>();
        Set<String> set = new HashSet<>();
        
        for(int i=0; i<=s.length()-10; i++) {
            String target = s.substring(i,i+10);
            if(set.contains(target)) {
                ans.add(target);
            }else {
                set.add(target);
            }
        }

        return new ArrayList<>(ans);
    }

189.旋轉陣列

該題目我們可以選擇暴力法解決,就是每次旋轉一次都將最後一位儲存,並且將第一位騰空,在賦值過去,時間複雜度 O(N * K)

有一種比較巧的辦法就是通過三次陣列反轉的方式完成的。

[1,2,3,4,5,6,7] k = 3
1.反轉整個陣列。[7,6,5,4,3,2,1]
2.反轉前k-1個陣列。[5,6,74,3,2,1]
3.反轉剩餘陣列。[5,6,7,1,2,3,4]

但是需要注意的是:

1、如果陣列長度為 1 ,那麼反轉就無意義。
2、如果反轉次數 > 陣列長度。說明有冗餘次數,沒旋轉一個陣列長度後陣列和原陣列無差別(一個週期),所以為了避免我們選擇將k與陣列長度取餘。

    //旋轉陣列,順時針,k為旋轉次數
    public void rotate(int[] nums, int k) {
        //只有一個元素,沒有反轉的意義
        if(nums.length == 1) {
            return;
        }
        //反轉陣列長度為一個週期, 多個週期也無意義
        k %= nums.length;
        //整體反轉
        reverse(nums,0,nums.length-1);
        //反轉前k個
        reverse(nums,0,k-1);
        //反轉剩餘部分
        reverse(nums,k,nums.length-1);
    }

    public void reverse(int[] nums, int left, int right) {
        while(left < right) {
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
            left++;
            right--;
        }
    }

190.顛倒二進位制位

有點類似於陣列的反轉,只不過這裡是二進位制的反轉。

這裡使用的是位操作,通過每次獲取n的最低位的情況(是 0/1,通過 n & 1獲得),加上對應的位數結果,達到反轉的效果,需要記得將結果左移,將n右移。

    //顛倒二進位制,類似於二進位制的反轉
    public int reverseBits(int n) {
        int ans = 0;

        for(int i=0; i<32; i++) {
            //ans左移
            ans <<= 1;
            //獲得n的最低位,作為ans的最高位
            ans += n & 1;
            //n右移
            n >>= 1;
        }

        return ans;
    }

191.二進位制中1的個數

是位運算章節的題目,我們需要對二進位制中的 1 進行計數,可以通過使用一個mask用於和二進位制中最右邊的未檢測的位進行與運算(如果二進位制中為0則與的結果為0,否則為1即將計數器++),之後再講mask左移一位,使其能和下一位進行與運算判斷。

    //二進位制中 1 的個數
    public int hammingWeight(int n) {
        int ans = 0;
        int mask = 1;
        for(int i=0; i<32; i++) {
        	//說明二進位制對應位為1,計數器 +1 
            if((n & mask) != 0) {
                ans++;
            }
            mask <<= 1;
        }
        return ans;
    }

相關文章