2021-7-10 LeetCode
刪除元素
就是給你一個數組移除值為指定值的項。其實就是一個簡單的陣列移位。把後面的所有元素往前挪一位。
很簡單,就不贅述了
程式碼:
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; }
二叉樹的最大深度
就是 ,給你一個二叉樹,讓你求這棵樹的最大深度
程式碼:
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; }
二叉樹的最近公共祖先
和前面差不多,只是二叉搜尋樹換了二叉樹。
思路也差不多。
程式碼:
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;
}
最小棧
設計一個支援 push
,pop
,top
操作,並能在常數時間內檢索到最小元素的棧。
- 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;
}
}
反轉連結串列
給你單鏈表的頭節點 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;
}
環形連結串列
給定一個連結串列,判斷連結串列中是否有環。
這道題比較簡單,就用兩個指標,一個步數為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
在上一題基礎上找出環的入口那個節點
還是用快慢指標,不過找到環位置需要簡單的畫圖列表達式。不方便畫圖,直接看官方題解吧。
程式碼:
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;
}
相交連結串列
給你兩個單鏈表的頭節點 headA
和 headB
,請你找出並返回兩個單鏈表相交的起始節點。如果兩個連結串列沒有交點,返回 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;
}
不得不說,這程式碼真的很優雅。
多數元素
給定一個大小為 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;
}