1. 程式人生 > 其它 >2021-7-10 LeetCode

2021-7-10 LeetCode

昨天鴿了一天,今天多做幾道算是補上吧,但目前做的難度都是簡單的,以及基本避開了動態規劃類題,等學了再做其他難度的題以及動態規劃吧

刪除元素

27. 移除元素 - 力扣(LeetCode) (leetcode-cn.com)

就是給你一個數組移除值為指定值的項。其實就是一個簡單的陣列移位。把後面的所有元素往前挪一位。

很簡單,就不贅述了

程式碼:

public int removeElement(int[] nums, int val) {
    int len = nums.length;
    for (int i = 0; i < len; i++) {
        if (nums[i] == val) {
            if (len - 1 - i >= 0) System.arraycopy(nums, i + 1, nums, i, len - 1 - i);
            len--;
            i--;
        }
    }
    return len;
}

二叉樹的最大深度

104. 二叉樹的最大深度 - 力扣(LeetCode) (leetcode-cn.com)

就是 ,給你一個二叉樹,讓你求這棵樹的最大深度

程式碼:

public int maxDepth(TreeNode root) {
    if (root == null)
        return 0;
    int maxR = maxDepth(root.right);
    int maxL = maxDepth(root.left);
    return Math.max(maxR, maxL) + 1;
}

二叉搜尋樹的最近公共祖先

235. 二叉搜尋樹的最近公共祖先 - 力扣(LeetCode) (leetcode-cn.com)

給定一個二叉搜尋樹, 找到該樹中兩個指定節點的最近公共祖先。

思路:

因為是二叉搜尋樹,當根節點大於p和q時向左遞迴,當根節點小於p和q時向右遞迴,當root介於p和q之間時,說明root就是他們的公共祖先。

遞迴停止條件:當前節點值和p或q相等,說明找到了這個節點,直接返回

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val == p.val || root.val == q.val)
            return root;
        if (root.val > p.val && root.val > q.val)
            return lowestCommonAncestor(root.left, p, q);
        else if (root.val < p.val && root.val < q.val)
            return lowestCommonAncestor(root.right, p, q);
       return root;
    }

二叉樹的最近公共祖先

236. 二叉樹的最近公共祖先 - 力扣(LeetCode) (leetcode-cn.com)

和前面差不多,只是二叉搜尋樹換了二叉樹。

思路也差不多。

程式碼:

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || root.val == p.val || root.val == q.val)
        return root;
    TreeNode r = lowestCommonAncestor(root.right, p, q);
    TreeNode l = lowestCommonAncestor(root.left, p, q);
    if (r != null && l != null )
        return root;
    return r == null ? l : r;
}

最小棧

155. 最小棧 - 力扣(LeetCode) (leetcode-cn.com)

設計一個支援 pushpoptop 操作,並能在常數時間內檢索到最小元素的棧。

  • push(x) —— 將元素 x 推入棧中。
  • pop() —— 刪除棧頂的元素。
  • top() —— 獲取棧頂元素。
  • getMin() —— 檢索棧中的最小元素。

思路:

最開始想得比較簡單,在類裡放一個min變數,用來存最小值。每次入棧時檢查下是否應該更新這個最小值。

當然 ,肯定沒這麼簡單,很快我就發現了問題,如果出棧的時候,剛好是最小值這個點怎麼辦。例如:

push:[0,1,2,-1],在這幾次入棧後顯然最小值為-1,這時我們遇到了問題,-1已經不存在了,可我們存的最小值還是-1。

遇到這個問題後,第一反應是出棧也進行一次比較,出棧的時候看看是不是出的最小值那個。是的話就把min更新為第二小。

可這個第二小怎麼來呢?哪個值是第二小呢?

這個答案很簡單,min未被更新時的值就是第二小。來看一下min被更新的過程,例如:開始 min = 0,入棧push(-1), min => -1,此時0就是第二小。

所以我們需要知道當一個值入棧時,當前最小值為多少。這樣一來,當最小值被出棧的時候,可以通過記錄下來的這個上一個最小值去更新min。

程式碼:

class DataMin {
   int data;
   int min;   // 此刻的最小值
   public DataMin(int data, int min) {
       this.data = data;
       this.min = min;
   }
}
class MinStack {
   int min = Integer.MAX_VALUE;    // 初始狀態無最小值,所以把最小值初始化為最大值
   Stack<DataMin> stack = new Stack<>();
   public void pop() {
       DataMin dm = stack.pop();
       this.min = Math.max(dm.min, this.min);  // 判斷出棧的值是否是最小值,若是最小值則更新min為上一個最小值
   }

   public void push(int x) {
       DataMin dm = new DataMin(x, this.min);   // 打包當前的最小值和x
       this.min = Math.min(x, this.min);   // 若x<min 更新最小值
       stack.push(dm);
   }

   public int top() {
       return stack.peek().data;
   }

   public int getMin() {
       return this.min;
   }
}

反轉連結串列

206. 反轉連結串列 - 力扣(LeetCode) (leetcode-cn.com)

給你單鏈表的頭節點 head ,請你反轉連結串列,並返回反轉後的連結串列。

輸入:head = [1,2,3,4,5]
輸出:[5,4,3,2,1]

思路:

這個比較簡單,就是將每一個節點的next由指向下一個節點改為上一個節點(需要提前儲存好下一個節點,因為改變next後若未儲存,則將找不到下一個節點)

程式碼:

public ListNode reverseList(ListNode head) {
    if (head == null || head.next == null)
        return head;
    ListNode pre = null;   // 前一個節點
    ListNode tail = head;    // 當前節點
    while(tail != null) {
        ListNode tmp = tail.next;   // 記錄下一個節點
        tail.next = pre;    // 指向前一個節點
        pre = tail;     // 將前一個節點更新為當前節點
        tail = tmp ;
    }
    return pre;
}

環形連結串列

141. 環形連結串列 - 力扣(LeetCode) (leetcode-cn.com)

給定一個連結串列,判斷連結串列中是否有環。

這道題比較簡單,就用兩個指標,一個步數為2一個步數為1,用快指標去追慢指標就行了,能追到就有環,沒追到就沒環

public boolean hasCycle(ListNode head) {
    ListNode fast = head;
    ListNode slow = head;
    while(fast != null && fast.next != null){
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast)
            return true;
    }
    return false;
}

環形連結串列 II

142. 環形連結串列 II - 力扣(LeetCode) (leetcode-cn.com)

在上一題基礎上找出環的入口那個節點

還是用快慢指標,不過找到環位置需要簡單的畫圖列表達式。不方便畫圖,直接看官方題解吧。

程式碼:

public ListNode detectCycle(ListNode head) {
    ListNode slow = head, fast = head;    // 快慢指標
    ListNode tmp = head;

    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (slow == fast) {
            while (slow != tmp) {
                slow = slow.next;
                tmp = tmp.next;
            }
            return tmp;
        }
    }
    return null;
}

相交連結串列

160. 相交連結串列 - 力扣(LeetCode) (leetcode-cn.com)

給你兩個單鏈表的頭節點 headAheadB ,請你找出並返回兩個單鏈表相交的起始節點。如果兩個連結串列沒有交點,返回 null

相交指NodeListA == NodeListB 而非NodeListA.val == NodeListB.val

我的思路:

先將任意一個連結串列遍歷到鏈尾,再將這個鏈尾與另一個連結串列的頭連線。例如連結串列A的尾為tail。則tail.next = headB。此時,若兩個連結串列不相交,則相當於把兩個連結串列連成了一個長連結串列。若相交,則現在的連結串列必將成為一個環。

有了環再用上一題做法找到相交點。

這裡由於前面做有環連結串列那個用快慢指標的影響,侷限了思維。在這裡都沒仔細考慮就無腦用快慢指標了。在這個題裡,若存在環,尾節點必在環中,只需要看尾節點有沒有重複出現就行了。

程式碼:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    ListNode tail = headA;
    // 找到連結串列A的尾節點
    while(tail.next != null) {
        tail = tail.next;
    }
    tail.next = headB;  // 將A尾節點與B頭節點相連

    ListNode slow = headA, fast = headA;    // 快慢指標
    ListNode tmp = headA;
    boolean flag = false;   // 是否存在環
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (slow == fast) {
            while (slow != tmp) {
                slow = slow.next;
                tmp = tmp.next;
            }
            flag = true;
            break;
        }
    }
    tail.next = null;
    return flag ? tmp : null;
}

去評論區看了下,有個大致想法和我差不多,但優雅太多了,學習了。

評論區看的思路:

大致思路一樣,用一個連結串列的尾去接另一個連結串列的頭。但不同的是他兩個表都做了這樣的操作。就會形成兩個環,然後同時遍歷這兩個環來找到交叉點。這樣一來,當第二個環形成時的那個節點,相當於用快慢指標做法的快慢指標相遇的那個點,此時在同時遍歷的兩個指標,一個相當於快慢指標做法中的慢指標,一個相當於相遇後再從起點放出來的那個指標。文字敘述可能有些敘述不清楚,畫出圖很簡單就能理清楚。

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        /**
        定義兩個指標, 第一輪讓兩個到達末尾的節點指向另一個連結串列的頭部, 最後如果相遇則為交點(在第一輪移動中恰好抹除了長度差)
        兩個指標等於移動了相同的距離, 有交點就返回, 無交點就是各走了兩條指標的長度
        **/
        if(headA == null || headB == null) return null;
        ListNode pA = headA, pB = headB;
        // 在這裡第一輪體現在pA和pB第一次到達尾部會移向另一連結串列的表頭, 而第二輪體現在如果pA或pB相交就返回交點, 不相交最後就是null==null
        while(pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }

不得不說,這程式碼真的很優雅。

多數元素

169. 多數元素 - 力扣(LeetCode) (leetcode-cn.com)

給定一個大小為 n 的陣列,找到其中的多數元素。多數元素是指在陣列中出現次數 大於 ⌊ n/2 ⌋ 的元素。

輸入:[2,2,1,1,1,2,2]
輸出:2

思路:

這個我自己想出的都是無腦暴力的。

看了題解,有個思路我覺得很好理解。

這裡引用評論區的解釋:

摩爾投票法:

核心就是對拼消耗。

玩一個諸侯爭霸的遊戲,假設你方人口超過總人口一半以上,並且能保證每個人口出去幹仗都能一對一同歸於盡。最後還有人活下來的國家就是勝利。

那就大混戰唄,最差所有人都聯合起來對付你(對應你每次選擇作為計數器的數都是眾數),或者其他國家也會相互攻擊(會選擇其他數作為計數器的數),但是隻要你們不要內鬥,最後肯定你贏。

最後能剩下的必定是自己人。

理一下,就是先抽一個國家的人上場,等待下一個人來和他打架,若抽出來的和自己是同一個國家的,那麼自然不能打架。此時計數器+1,代表此時場上有count個這個國家的人,若上場的非同一國家,則他們打架同歸於盡,計數器-1,場上這個國家的人少了一個。當此國家場上的人數等於0時說明,他們目前沒人了,所以場上的主導國家換了一個。這樣不斷的對拼消耗,最終場上留下來的國家,一定是人數大於n/2的那個國家。

public int majorityElement(int[] nums) {
    int tmp = nums[0];
    int cnt = 0;
    for (int i : nums) {
        cnt += tmp == i ? 1 : -1;
        if (cnt == 0) {
            tmp = i;	//	更換國家主導戰場
            cnt = 1;	//	初始化此國家場上人數為1
        }
    }
    return tmp;
}