極客時間算法40講 簡述 題目

簡述

極客時間算法40講中所出現的leetcode算法題

題目

【鏈表】reverse-linked-list(反轉一個單鏈表)

示例:
輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL

代碼

遞歸

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode node = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return node;
    }
}

迭代

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        while(head != null){
            ListNode tmp = head.next;
            head.next = prev;
            prev = head;
            head = tmp;
        }
        return prev;
    }
}

【鏈表】swap-nodes-in-pairs(兩兩交換鏈表中的節點)

給定一個鏈表,兩兩交換其中相鄰的節點,並返回交換後的鏈表。
你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。

示例:
給定 1->2->3->4, 你應該返回 2->1->4->3.

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode res = head.next;
        ListNode pre = new ListNode(-1);
        pre.next = head;
        while(pre.next != null && pre.next.next != null){
            ListNode a = pre.next;
            ListNode b = a.next;
            pre.next = b;
            a.next = b.next;
            b.next = a;
            pre = a;
        }
        return res;
    }
}

【鏈表】linked-list-cycle(環形鏈表)

給定一個鏈表,判斷鏈表中是否有環。

爲了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos-1,則在該鏈表中沒有環。

示例 1:

輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部連接到第二個節點。

示例 2:

輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部連接到第一個節點。

示例 3:

輸入:head = [1], pos = -1
輸出:false
解釋:鏈表中沒有環。

代碼

雙指針

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null || head.next.next == null) return false;
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }
}

set檢測重複

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while(head != null){
            if(set.contains(head)) return true;
            set.add(head);
            head = head.next;
        }
        return false;
    }
}


【鏈表】linked-list-cycle-ii(環形鏈表 II)

給定一個鏈表,返回鏈表開始入環的第一個節點。 如果鏈表無環,則返回 null

爲了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos-1,則在該鏈表中沒有環。

說明:不允許修改給定的鏈表。

示例 1:

輸入:head = [3,2,0,-4], pos = 1
輸出:tail connects to node index 1
解釋:鏈表中有一個環,其尾部連接到第二個節點。

示例 2:

輸入:head = [1,2], pos = 0
輸出:tail connects to node index 0
解釋:鏈表中有一個環,其尾部連接到第一個節點。

示例 3:

輸出:no cycle
解釋:鏈表中沒有環。

代碼

雙指針

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        boolean isCycle = false;
        ListNode a = head;//慢指針
        ListNode b = head;//快指針
        while(b != null && b.next != null){
            a = a.next;
            b = b.next.next;
            if(a == b){
                isCycle = true;
                break;
            }
        }
        if(isCycle){
            ListNode c = head;
            while(c != a){
                c = c.next;
                a = a.next;
            }
            return a;
        }else return null;
    }
}

set判斷重複

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while(head != null){
            if(set.contains(head)) return head;
            set.add(head);
            head = head.next;
        }
        return null;
    }
}


【鏈表】reverse-nodes-in-k-group(k個一組翻轉鏈表)

給出一個鏈表,每 k 個節點一組進行翻轉,並返回翻轉後的鏈表。
k 是一個正整數,它的值小於或等於鏈表的長度。如果節點總數不是 k 的整數倍,那麼將最後剩餘節點保持原有順序。

示例 :
給定這個鏈表:1->2->3->4->5
當 k = 2 時,應當返回: 2->1->4->3->5
當 k = 3 時,應當返回: 3->2->1->4->5

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode tmp = new ListNode(-1);
        ListNode pre = tmp;
        ListNode cur = pre;
        tmp.next = head;
        int num = 0;
        while(cur != null){
            cur = cur.next;
            num ++;
        }
        while(num > k){
            cur = pre.next;
            for(int i = 1; i < k; i ++){
                ListNode t = cur.next;
                cur.next = t.next;
                t.next = pre.next;
                pre.next = t;
            }
            pre = cur;
            num -= k;
        }
        return tmp.next;
        
    }
}


【棧】valid-parentheses(有效的括號)

給定一個只包括 '('')''{''}''['']' 的字符串,判斷字符串是否有效。

有效字符串需滿足:

  1. 左括號必須用相同類型的右括號閉合。
  2. 左括號必須以正確的順序閉合。

注意空字符串可被認爲是有效字符串。

示例 1:

輸入: "()"
輸出: true

示例 2:

輸入: "()[]{}"
輸出: true

示例 3:

輸入: "(]"
輸出: false

示例 4:

輸入: "([)]"
輸出: false

示例 5:

輸入: "{[]}"
輸出: true

代碼

class Solution {
    public boolean isValid(String s) {
        Map<Character, Character> map = new HashMap<>();
        map.put(')', '(');
        map.put('}', '{');
        map.put(']', '[');
        Stack<Character> stack = new Stack<>();
        
        char[] arr = s.toCharArray();
        for(char c : arr){
            if(!map.containsKey(c)) stack.push(c);
            else if(stack.isEmpty() || map.get(c) != stack.pop()) return false;
        }
        return stack.isEmpty();
    }
}


【隊列&棧】implement-stack-using-queues(用隊列實現棧)

使用隊列實現棧的下列操作:

  • push(x) -- 元素 x 入棧
  • pop() -- 移除棧頂元素
  • top() -- 獲取棧頂元素
  • empty() -- 返回棧是否爲空

代碼

class MyStack {

    Queue<Integer> q;
    /** Initialize your data structure here. */
    public MyStack() {
        q = new LinkedList<>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        q.offer(x);
        int n = q.size();
        for(int i = 0; i < n - 1; i ++){
            q.offer(q.poll());
        }
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return q.poll();
    }
    
    /** Get the top element. */
    public int top() {
        return q.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return q.isEmpty();
    }
}


【隊列&棧】Implement Queue using Stacks(用棧實現隊列)

使用棧實現隊列的下列操作:

  • push(x) -- 將一個元素放入隊列的尾部。
  • pop() -- 從隊列首部移除元素。
  • peek() -- 返回隊列首部的元素。
  • empty() -- 返回隊列是否爲空。

代碼

class MyQueue {

    Stack<Integer> s1, s2;
    /** Initialize your data structure here. */
    public MyQueue() {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    
    /** Push element x to the back of queue. */
    public void push(int x) {
        s1.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    public int pop() {
        if(!s2.isEmpty()) return s2.pop();
        else{
            while(!s1.isEmpty()){
                s2.push(s1.pop());
            }
            return s2.pop();
        }
    }
    
    /** Get the front element. */
    public int peek() {
        if(!s2.isEmpty()) return s2.peek();
        else{
            while(!s1.isEmpty()){
                s2.push(s1.pop());
            }
            return s2.peek();
        }
    }
    
    /** Returns whether the queue is empty. */
    public boolean empty() {
        return s1.isEmpty() && s2.isEmpty();
    }
}


【優先隊列】kth-largest-element-in-a-stream(數據流中的第K大元素)

設計一個找到數據流中第K大元素的類(class)。注意是排序後的第K大元素,不是第K個不同的元素。

你的 KthLargest類需要一個同時接收整數k 和整數數組nums的構造器,它包含數據流中的初始元素。每次調用 KthLargest.add,返回當前數據流中第K大的元素。

示例:
int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3);   // returns 4
kthLargest.add(5);   // returns 5
kthLargest.add(10);  // returns 5
kthLargest.add(9);   // returns 8
kthLargest.add(4);   // returns 8

說明:
你可以假設 nums 的長度≥ k-1 且k ≥ 1。

代碼

class KthLargest {

    PriorityQueue<Integer> q;
    int k;
    public KthLargest(int k, int[] nums) {
        this.k = k;
        q = new PriorityQueue<>();
        for(int i : nums) add(i);
    }
    
    public int add(int val) {
        if(q.size() < k) q.offer(val);
        else if(val > q.peek()){
            q.poll();
            q.offer(val);
        }
        return q.peek();
    }
}


【優先隊列】sliding-window-maximum(滑動窗口最大值)

給定一個數組 nums,有一個大小爲 k 的滑動窗口從數組的最左側移動到數組的最右側。你只可以看到在滑動窗口 k 內的數字。滑動窗口每次只向右移動一位。

返回滑動窗口最大值。

示例:

輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7] 
解釋: 

  滑動窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

注意:

你可以假設 k 總是有效的,1 ≤ k ≤ 輸入數組的大小,且輸入數組不爲空。

代碼

優先隊列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0) return new int[0];
        int[] res = new int[nums.length - k + 1];
        PriorityQueue<Integer> q = new PriorityQueue<>(k, Comparator.reverseOrder());
        for(int i = 0; i < k; i ++){
            q.offer(nums[i]);
        }
        res[0] = q.peek();
        int index = 1;
        for(int i = k; i < nums.length; i ++){
            q.remove(nums[index - 1]);
            q.offer(nums[i]);
            res[index++] = q.peek();
        }
        return res;
    }
}

雙端隊列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0) return new int[0];
        int[] res = new int[nums.length - k + 1];
        Deque<Integer> q = new LinkedList<>();
        int index = 0;
        for(int i = 0; i < nums.length; i ++){
            while(!q.isEmpty() && nums[q.peekLast()] <= nums[i]){
                q.pollLast();
            }
            q.offerLast(i);
            if(q.peek() == i - k) q.pollFirst();
            if(i >= k - 1) res[index ++] = nums[q.peek()];
        }
        return res;
    }
}


【哈希】valid-anagram(有效的字母異位詞)

給定兩個字符串 st ,編寫一個函數來判斷 t 是否是 s 的一個字母異位詞。

示例 1:

輸入: s = "anagram", t = "nagaram"
輸出: true

示例 2:

輸入: s = "rat", t = "car"
輸出: false

說明:
你可以假設字符串只包含小寫字母。

代碼

hashmap

class Solution {
    public boolean isAnagram(String s, String t) {
        Map<Character, Integer> sMap = new HashMap<>();
        Map<Character, Integer> tMap = new HashMap<>();
        char[] sC = s.toCharArray();
        char[] sT = t.toCharArray();
        for(char c : sC){
            sMap.put(c, sMap.getOrDefault(c, 0) + 1);
        }
        
        for(char c : sT){
            tMap.put(c, tMap.getOrDefault(c, 0) + 1);
        }
        return sMap.equals(tMap);
    }
}

數組映射

class Solution {
    public boolean isAnagram(String s, String t) {
        int[] a = new int[26];
        int[] b = new int[26];
        
        char[] sC = s.toCharArray();
        char[] tC = t.toCharArray();
        
        for(char c : sC){
            a[c - 'a'] += 1;
        }
        
        for(char c : tC){
            b[c - 'a'] += 1;
        }
        return Arrays.equals(a, b);
    }
}


【哈希】two-sum(兩數之和)

給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。

你可以假設每種輸入只會對應一個答案。但是,你不能重複利用這個數組中同樣的元素。

示例:

給定 nums = [2, 7, 11, 15], target = 9

因爲 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

代碼

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums.length; i ++){
            if(map.containsKey(target - nums[i])) {
                return new int[]{map.get(target - nums[i]), i};
            }
            map.put(nums[i], i);
        }
        return new int[2];
    }
}


【哈希】3sum(三數之和)

給定一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重複的三元組。

注意:答案中不可以包含重複的三元組。

例如, 給定數組 nums = [-1, 0, 1, 2, -1, -4],

滿足要求的三元組集合爲:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

代碼

排序後枚舉

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        Set<List<Integer>> res = new HashSet<>();
        for(int i = 0; i < nums.length - 1; i ++){   
            if(i > 0 && nums[i] == nums[i - 1]) continue;
            Set<Integer> set = new HashSet<>();
            for(int j = i + 1; j < nums.length; j ++){
                if(!set.contains(nums[j])) set.add(-nums[i]-nums[j]);
                else {
                    res.add(Arrays.asList(nums[i], -nums[i] - nums[j], nums[j]));
                }
            }
        }
        return new ArrayList<>(res);
    }
}

排序後使用雙指針

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        for(int i = 0; i < nums.length - 1; i ++){
            if(i > 0 && nums[i] == nums[i - 1]) continue; 
            int start = i + 1;
            int end = nums.length - 1;
            while(start < end){
                int sum = nums[i] + nums[start] + nums[end];
                if(sum > 0) end --;
                else if(sum < 0) start ++;
                else {
                    res.add(Arrays.asList(nums[i], nums[start], nums[end]));
                    //判重
                    while(start < end && nums[start] == nums[start + 1]) start ++;
                    while(start < end && nums[end] == nums[end - 1]) end --;
                    start ++;
                    end --;
                }
            }
        }
        return res;
    }
}


【二叉樹】validate-binary-search-tree(驗證二叉搜索樹)

給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹。

假設一個二叉搜索樹具有如下特徵:

  • 節點的左子樹只包含小於當前節點的數。
  • 節點的右子樹只包含大於當前節點的數。
  • 所有左子樹和右子樹自身必須也是二叉搜索樹。

示例 1:

輸入:
    2
   / \
  1   3
輸出: true

示例 2:

輸入:
    5
   / \
  1   4
     / \
    3   6
輸出: false
解釋: 輸入爲: [5,1,4,null,null,3,6]。
     根節點的值爲 5 ,但是其右子節點值爲 4 。

代碼

利用中序遍歷的有序性

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    
    List<Integer> list = new ArrayList<>();
    
    public boolean isValidBST(TreeNode root) {
        midOrder(root);
        if(list.size() == 0) return true;
        int tmp = list.get(0);
        for(int i = 1,length = list.size(); i < length; i ++){
            if(list.get(i) <= tmp){
                return false;
            }
            tmp = list.get(i);
        }
        return true;
    }
    
    public void midOrder(TreeNode root){
        if(root != null){
            midOrder(root.left);
            list.add(root.val);
            midOrder(root.right);
        }
    }
}

遞歸

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    TreeNode pre = null;
    public boolean isValidBST(TreeNode root) {
        if(root == null) return true;
        if(!isValidBST(root.left)) return false;
        if(pre != null && pre.val >= root.val) return false;
        pre = root;
        return isValidBST(root.right);
    }
    
}


【二叉樹】lowest-common-ancestor-of-a-binary-search-tree(二叉搜索樹的最近公共祖先)

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

百度百科中最近公共祖先的定義爲:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示爲一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度儘可能大(一個節點也可以是它自己的祖先)。”

例如,給定如下二叉搜索樹: root = [6,2,8,0,4,7,9,null,null,3,5]

代碼

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root != null){
            if(p.val > root.val && q.val > root.val) root = root.right;
            else if(p.val < root.val && q.val < root.val) root = root.left;
            else return root;
        }    
        return null;
    }
}


【遞歸&分治】Pow(x, n)

實現 pow(x, n) ,即計算 x 的 n 次冪函數。

示例 1:

輸入: 2.00000, 10
輸出: 1024.00000

示例 2:

輸入: 2.10000, 3
輸出: 9.26100

示例 3:

輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25

說明:

  • -100.0 < x < 100.0
  • n 是 32 位有符號整數,其數值範圍是 [−231, 231 − 1] 。

代碼

class Solution {
    public double myPow(double x, int n) {
        if (n < 0) return 1.0 / myPow(x, -n);
        if (n == 0) return 1;
        double v = myPow(x, n / 2);
        return n % 2 == 0 ? v * v : v * v * x;
    }
}


【計數】majority-element(求衆數)

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

你可以假設數組是非空的,並且給定的數組總是存在衆數。

示例 1:

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

示例 2:

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

代碼

利用map計數

class Solution {
    public int majorityElement(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0, length = nums.length; i < length; i ++){
            if(map.containsKey(nums[i])){
                map.put(nums[i], map.get(nums[i]) + 1);
            }else{
                map.put(nums[i], 1);
            }
        }
        Set<Integer> set = map.keySet();
        for(Integer key : set){
            if(map.get(key) > nums.length/2) return key;
        }
        return 0;
    }
}

排序計數

class Solution {
    public int majorityElement(int[] nums) {
        if(nums.length == 1) return nums[0];
        Arrays.sort(nums);
        int count = 1;
        for(int i = 1; i < nums.length; i ++){
            if(nums[i] == nums[i-1]){
                count ++;
            } else count = 1; 
            if(count > nums.length / 2) return nums[i];
        }
        return 0;
    }
}


【貪心】best-time-to-buy-and-sell-stock-ii(買賣股票的最佳時機 II)

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
     因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

代碼

class Solution {
    public int maxProfit(int[] prices) {
        if(prices == null || prices.length == 0) return 0;
        int cur = prices[0];
        int length = prices.length;
        int res = 0;
        for(int i = 0; i < length; i ++){
            if(prices[i] > cur){
                res += (prices[i] - cur);
                cur = prices[i];
            }else{
                cur = prices[i];
            }
        }   
        return res;
    }
}


【BFS】 廣度優先搜索 (僞碼)

BFS()
{
  輸入起始點;
  初始化所有頂點標記爲未遍歷;
  初始化一個隊列queue並將起始點放入隊列;

  while(queue不爲空)
  {
    從隊列中刪除一個頂點s並標記爲已遍歷; 
    將s鄰接的所有還沒遍歷的點加入隊列;
  }
}


【DFS】 深度優先搜索(僞碼)

DFS(頂點v)
{
  標記v爲已遍歷;
  for(對於每一個鄰接v且未標記遍歷的點u)
      DFS(u);
}


【BFS】binary-tree-level-order-traversal(二叉樹的層次遍歷)

給定一個二叉樹,返回其按層次遍歷的節點值。 (即逐層地,從左到右訪問所有節點)。

例如:
給定二叉樹: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回其層次遍歷結果:

[
  [3],
  [9,20],
  [15,7]
]

代碼

bfs

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) return new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> result = new ArrayList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            List<Integer> node = new ArrayList<>();
            int length = queue.size();
            while (length > 0){
                TreeNode tree = queue.poll();
                if(tree.left != null){
                    queue.offer(tree.left);
                }
                if(tree.right != null){
                    queue.offer(tree.right);
                }
                node.add(tree.val);
                length --;
            }            
            result.add(node);
        }      
        return result;
    }
}

也可以用dfs

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) return new ArrayList<>();
        List<List<Integer>> res = new ArrayList<>();
        _dfs(res, root, 0);
        return res;
    }
    
    public void _dfs(List<List<Integer>> list, TreeNode node, int level){
        if(node == null) return;
        if(list.size() < level + 1){
            list.add(new ArrayList<>());
        }
        list.get(level).add(node.val);
        _dfs(list, node.left, level + 1);
        _dfs(list, node.right, level + 1);
    }
}


【DFS】maximum-depth-of-binary-tree(二叉樹的最大深度)

給定一個二叉樹,找出其最大深度。

二叉樹的深度爲根節點到最遠葉子節點的最長路徑上的節點數。

說明: 葉子節點是指沒有子節點的節點。

示例:
給定二叉樹 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

代碼

DFS

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        return root == null ? 0 : Math.max(maxDepth(root.left),maxDepth(root.right)) + 1; 
    }
}

BFS

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        int count = 1;
        while(!q.isEmpty()){
            int length = q.size();
            boolean flag = false;
            for(int i = 0; i < length; i ++){
                TreeNode node = q.poll();
                if(node.left != null || node.right != null) flag = true;
                if(node.left != null) q.offer(node.left);
                if(node.right != null) q.offer(node.right);
            }
            if(flag) count ++;
        }
        return count;
    }
}


【DFS】minimum-depth-of-binary-tree(二叉樹的最小深度)

給定一個二叉樹,找出其最小深度。

最小深度是從根節點到最近葉子節點的最短路徑上的節點數量。

說明: 葉子節點是指沒有子節點的節點。

示例:

給定二叉樹 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回它的最小深度 2.

代碼

BFS

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        int count = 1;
        while(!q.isEmpty()){
            int length = q.size();
            boolean flag = false;
            for(int i = 0; i < length; i ++){
                TreeNode node = q.poll();
                if(node.left == null && node.right == null) return count;
                if(node.left != null || node.right != null) flag = true;
                if(node.left != null) q.offer(node.left);
                if(node.right != null) q.offer(node.right);
            }
            if(flag) count ++;
        }
        return count;
    }
}

DFS

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int minDepth(TreeNode root) {
        if(null == root) return 0;
        if(null == root.left) return minDepth(root.right) + 1;
        if(null == root.right) return minDepth(root.left) + 1;
        return Math.min(minDepth(root.left),minDepth(root.right)) + 1;     
    }
}


【回溯】generate-parentheses(括號生成)

給出 n 代表生成括號的對數,請你寫出一個函數,使其能夠生成所有可能的並且有效的括號組合。

例如,給出 n = 3,生成結果爲:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

代碼

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        _gen(res, 0, 0, n, "");
        return res;
    }
    
    public void _gen(List<String> list, int left, int right, int n, String res){
        if(left == n && right == n){
            list.add(res);
            return;
        }
        if(left < n){
            _gen(list, left + 1, right, n, res + "(");
        }
        if(left > right && right < n){
            _gen(list, left, right + 1, n, res + ")");
        }
    }
}


【剪枝】n-queens(N皇后)

n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,並且使皇后彼此之間不能相互攻擊。

上圖爲 8 皇后問題的一種解法。

給定一個整數 n,返回所有不同的 n 皇后問題的解決方案。

每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 'Q''.' 分別代表了皇后和空位。

示例:

輸入: 4
輸出: [
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解釋: 4 皇后問題存在兩個不同的解法。

代碼

class Solution {
    private List<List<String>> res = new ArrayList<>();
    private Set<Integer> cols = new HashSet<>();
    private Set<Integer> pie = new HashSet<>();
    private Set<Integer> na = new HashSet<>();
    public List<List<String>> solveNQueens(int n) {
        if (n < 1) {
            return new ArrayList<>();
        }
        dfs(n, 0, new ArrayList<>());
        return res;
    }
    
    public void dfs(int n, int row, List<String> curState) {
        if (row >= n) {
            res.add(curState);
            return;
        }
        for (int i = 0; i < n; i ++) {
            if (cols.contains(i) || pie.contains(row + i) || na.contains(row - i)) {
                continue;
            }
            cols.add(i);
            pie.add(row + i);
            na.add(row - i);
            
            StringBuilder sb = new StringBuilder();
            for(int j = 0; j < n; j ++) {
                if (j == i) {
                    sb.append("Q");
                } else {
                    sb.append(".");
                }
            }
            List<String> tmp = new ArrayList(curState);
            tmp.add(sb.toString());
            dfs(n, row + 1, tmp);
            cols.remove(i);
            pie.remove(row + i);
            na.remove(row - i);
        }
        
    }
}


【哈希】valid-sudoku(有效的數獨)

判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。

  1. 數字 1-9 在每一行只能出現一次。
  2. 數字 1-9 在每一列只能出現一次。
  3. 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。

上圖是一個部分填充的有效的數獨。

數獨部分空格內已填入了數字,空白格用 '.' 表示。

示例 1:

輸入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
輸出: true

示例 2:

輸入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
輸出: false
解釋: 除了第一行的第一個數字從 5 改爲 8 以外,空格內其他數字均與 示例1 相同。
     但由於位於左上角的 3x3 宮內有兩個 8 存在, 因此這個數獨是無效的。

說明:

  • 一個有效的數獨(部分已被填充)不一定是可解的。
  • 只需要根據以上規則,驗證已經填入的數字是否有效即可。
  • 給定數獨序列只包含數字 1-9 和字符 '.'
  • 給定數獨永遠是 9x9 形式的。

代碼

class Solution {
    public boolean isValidSudoku(char[][] board) {
        // 記錄某行,某位數字是否已經被擺放
        boolean[][] row = new boolean[9][9];
        // 記錄某列,某位數字是否已經被擺放
        boolean[][] col = new boolean[9][9];
        // 記錄某 3x3 宮格內,某位數字是否已經被擺放
        boolean[][] block = new boolean[9][9];

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int num = board[i][j] - '1';
                    int blockIndex = i / 3 * 3 + j / 3;
                    if (row[i][num] || col[j][num] || block[blockIndex][num]) {
                        return false;
                    } else {
                        row[i][num] = true;
                        col[j][num] = true;
                        block[blockIndex][num] = true;
                    }
                }
            }
        }
        return true;
    }
}


【剪枝&回溯】sudoku-solver(解數獨)

編寫一個程序,通過已填充的空格來解決數獨問題。

一個數獨的解法需遵循如下規則

  1. 數字 1-9 在每一行只能出現一次。
  2. 數字 1-9 在每一列只能出現一次。
  3. 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。

空白格用 '.' 表示。

一個數獨。

答案被標成紅色。

Note:

  • 給定的數獨序列只包含數字 1-9 和字符 '.'
  • 你可以假設給定的數獨只有唯一解。
  • 給定數獨永遠是 9x9 形式的。

代碼

class Solution {
    
    public void solveSudoku(char[][] board) {
        if (board == null || board.length == 0) return;
        solve(board);
    }
    
    private boolean solve(char[][] board) {
        for (int i = 0; i < board.length; i ++) {
            for (int j = 0; j < board[0].length; j ++) {
                if (board[i][j] == '.') {
                    for (char c = '1'; c <= '9'; c ++) {
                        if (isValid(board, i, j, c)) {
                            board[i][j] = c;
                            if (solve(board)) {
                                return true;
                            } else {
                                board[i][j] = '.';//還原
                            }
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
    
    private boolean isValid(char[][] board, int row, int col, char c) {
        for (int i = 0; i < 9; i ++) {
            if(board[i][col] != '.' && board[i][col] == c) return false;
            if(board[row][i] != '.' && board[row][i] == c) return false;
            int blocki = 3 * (row / 3) + i / 3;
            int blockj = 3 * (col / 3) + i % 3;
            if(board[blocki][blockj] != '.' && board[blocki][blockj] == c) return false;
        }
        return true;
    }
}


【二分查找】返回指定元素下標(基本用法)

public int getTargetIndex(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    while (left <= right) {
        int mid = (left + right) / 2;
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}


【二分查找】sqrtx(x 的平方根)

實現 int sqrt(int x) 函數。

計算並返回 x 的平方根,其中 x 是非負整數。

由於返回類型是整數,結果只保留整數的部分,小數部分將被捨去。

示例 1:

輸入: 4
輸出: 2

示例 2:

輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842..., 
     由於返回類型是整數,小數部分將被捨去。

代碼

class Solution {
    public int mySqrt(int x) {
        if(x == 0 || x == 1) return x;
        int left = 1;
        int right = x;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (mid == x / mid) {
                return mid;
            } else if (mid > x / mid) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left - 1;
    }
}


【二分查找】valid-perfect-square(有效的完全平方數)

給定一個正整數 num,編寫一個函數,如果 num 是一個完全平方數,則返回 True,否則返回 False。

說明:不要使用任何內置的庫函數,如 sqrt

示例 1:

輸入:16
輸出:True

示例 2:

輸入:14
輸出:False

代碼

公式法--1+3+5+7+9+…+(2n-1)=n^2

class Solution {
    public boolean isPerfectSquare(int num) {
        int sum = 0;
        for(int i = 1; num > 0; i +=2){
            num -= i;
        }
        return num == 0;
    }
}

二分法

class Solution {
    public boolean isPerfectSquare(int num) {
        if(num == 0 || num == 1) return true;
        int left = 1;
        int right = num;
        int res = 0;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (mid == num / mid) {
                res = mid;
                break;
            } else if (mid > num / mid) {
                right = mid - 1;
            } else {
                left = mid + 1;
                res = mid;
            }
        }
        return res * res == num;
    }
}


【字典樹】implement-trie-prefix-tree(實現 Trie (前綴樹))

實現一個 Trie (前綴樹),包含 insert, search, 和 startsWith 這三個操作。

示例:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true

說明:

  • 你可以假設所有的輸入都是由小寫字母 a-z 構成的。
  • 保證所有輸入均爲非空字符串。

代碼

class Trie {
    
    Trie[] children = new Trie[26];
    boolean isEndOfWord = false;

    /** Initialize your data structure here. */
    public Trie() {
        
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        char[] arr = word.toCharArray();
        Trie[] childs = children;
        for (int i = 0; i < arr.length; i ++) {
            if (childs[arr[i] - 'a'] == null) {
                childs[arr[i] - 'a'] = new Trie();
            }
            if (i == arr.length - 1) {
                childs[arr[i] - 'a'].isEndOfWord = true;
            }
            childs = childs[arr[i] - 'a'].children;
        }
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        char[] arr = word.toCharArray();
        Trie[] childs = children;
        for (int i = 0; i < arr.length; i ++) {
            if (childs[arr[i] - 'a'] == null) {
                return false;
            }
            if (i == arr.length - 1 && !childs[arr[i] - 'a'].isEndOfWord) {
                return false;
            }
            childs = childs[arr[i] - 'a'].children;
        }
        return true;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith (String prefix) {
        char[] arr = prefix.toCharArray();
        Trie[] childs = children;
        for (int i = 0; i < arr.length; i ++) {
            if (childs[arr[i] - 'a'] == null) {
                return false;
            }
            childs = childs[arr[i] - 'a'].children;
        }
        return true;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */


【字典樹】word-search-ii(單詞搜索 II)

給定一個二維網格 board 和一個字典中的單詞列表 words,找出所有同時在二維網格和字典中出現的單詞。

單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母在一個單詞中不允許被重複使用。

示例:

輸入: 
words = ["oath","pea","eat","rain"] and board =
[
  ['o','a','a','n'],
  ['e','t','a','e'],
  ['i','h','k','r'],
  ['i','f','l','v']
]

輸出: ["eat","oath"]

說明:
你可以假設所有輸入都由小寫字母 a-z 組成。

提示:

  • 你需要優化回溯算法以通過更大數據量的測試。你能否早點停止回溯?
  • 如果當前單詞不存在於所有單詞的前綴中,則可以立即停止回溯。什麼樣的數據結構可以有效地執行這樣的操作?散列表是否可行?爲什麼? 前綴樹如何?如果你想學習如何實現一個基本的前綴樹,請先查看這個問題: 實現Trie(前綴樹)

代碼

class Solution {
    Set<String> res = new HashSet<>();
    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        for (String word : words) {
            trie.insert(word);
        }
        int m = board.length;
        int n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                dfs(board, visited, "", i, j, trie);
            }
        }
        return new ArrayList<>(res);
    }
    
    private void dfs(char[][] board, boolean[][] visited, String str, int x, int y, Trie trie) {
        if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) return;
        if (visited[x][y]) return;
        str += board[x][y];
        if (!trie.startsWith(str)) return;
        if (trie.search(str)) {
            res.add(str);
        }
        visited[x][y] = true;
        dfs(board, visited, str, x - 1, y, trie);
        dfs(board, visited, str, x + 1, y, trie);
        dfs(board, visited, str, x, y - 1, trie);
        dfs(board, visited, str, x, y + 1, trie);
        visited[x][y] = false;
    }
}
class Trie {
    
    Trie[] children = new Trie[26];
    boolean isEndOfWord = false;

    /** Initialize your data structure here. */
    public Trie() {
        
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        char[] arr = word.toCharArray();
        Trie[] childs = children;
        for (int i = 0; i < arr.length; i ++) {
            if (childs[arr[i] - 'a'] == null) {
                childs[arr[i] - 'a'] = new Trie();
            }
            if (i == arr.length - 1) {
                childs[arr[i] - 'a'].isEndOfWord = true;
            }
            childs = childs[arr[i] - 'a'].children;
        }
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        char[] arr = word.toCharArray();
        Trie[] childs = children;
        for (int i = 0; i < arr.length; i ++) {
            if (childs[arr[i] - 'a'] == null) {
                return false;
            }
            if (i == arr.length - 1 && !childs[arr[i] - 'a'].isEndOfWord) {
                return false;
            }
            childs = childs[arr[i] - 'a'].children;
        }
        return true;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith (String prefix) {
        char[] arr = prefix.toCharArray();
        Trie[] childs = children;
        for (int i = 0; i < arr.length; i ++) {
            if (childs[arr[i] - 'a'] == null) {
                return false;
            }
            childs = childs[arr[i] - 'a'].children;
        }
        return true;
    }
}


【位運算】number-of-1-bits(位1的個數)

編寫一個函數,輸入是一個無符號整數,返回其二進制表達式中數字位數爲 ‘1’ 的個數(也被稱爲漢明重量)。

示例 1:

輸入:00000000000000000000000000001011
輸出:3
解釋:輸入的二進制串 00000000000000000000000000001011 中,共有三位爲 '1'。

示例 2:

輸入:00000000000000000000000010000000
輸出:1
解釋:輸入的二進制串 00000000000000000000000010000000 中,共有一位爲 '1'。

示例 3:

輸入:11111111111111111111111111111101
輸出:31
解釋:輸入的二進制串 11111111111111111111111111111101 中,共有 31 位爲 '1'。

提示:

  • 請注意,在某些語言(如 Java)中,沒有無符號整數類型。在這種情況下,輸入和輸出都將被指定爲有符號整數類型,並且不應影響您的實現,因爲無論整數是有符號的還是無符號的,其內部的二進制表示形式都是相同的。
  • 在 Java 中,編譯器使用二進制補碼記法來表示有符號整數。因此,在上面的 示例 3 中,輸入表示有符號整數 -3

代碼

遍歷每一位是否爲1

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count = 0;
        for (int i = 0; i < 32; i ++) {
            count += n & 1;
            n >>= 1;
        }
        return count;
    }
}

逐步打掉最後的1

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count = 0;
        while (n != 0) {
            count ++;
            n = n & (n-1);
        }
        return count;
    }
}


【位運算】power-of-two(2的冪)

給定一個整數,編寫一個函數來判斷它是否是 2 的冪次方。

示例 1:

輸入: 1
輸出: true
解釋: 20 = 1

示例 2:

輸入: 16
輸出: true
解釋: 24 = 16

示例 3:

輸入: 218
輸出: false

代碼

迭代判斷

class Solution {
    public boolean isPowerOfTwo(int n) {
        if (n < 1) return false;
        while (n > 1) {
            if(n % 2 != 0) return false;
            n /= 2;
        }
        return true;
    }
}

位運算(2的冪二進制只有一個1)

class Solution {
    public boolean isPowerOfTwo(int n) {
        return n > 0 && (n & (n - 1)) == 0;
    }
}


【位運算】counting-bits(比特位計數)

給定一個非負整數 num。對於 0 ≤ i ≤ num 範圍中的每個數字 i ,計算其二進制數中的 1 的數目並將它們作爲數組返回。

示例 1:

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

示例 2:

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

進階:

  • 給出時間複雜度爲O(n*sizeof(integer))的解答非常容易。但你可以在線性時間O(n)內用一趟掃描做到嗎?
  • 要求算法的空間複雜度爲O(n)
  • 你能進一步完善解法嗎?要求在C++或任何其他語言中不使用任何內置函數(如 C++ 中的 __builtin_popcount)來執行此操作。

代碼

求每個數的1的數目

class Solution {
    public int[] countBits(int num) {
        int[] res = new int[num + 1];
        for (int i = 0; i <= num; i ++) {
            res[i] = get1num(i);
        }
        return res;
    }
    
    private int get1num(int num) {
        int count = 0;
        while (num != 0) {
            count ++;
            num &= num-1;
        }
        return count;
    }
}

利用位運算 i&(i-1)要比i少一個1

class Solution {
    public int[] countBits(int num) {
        int[] res = new int[num + 1];
        for (int i = 1; i <= num; i ++) {
            res[i] = res[i & (i - 1)] + 1;
        }
        return res;
    }
}


【動態規劃】fibonacci-number(斐波那契數)

斐波那契數,通常用 F(n) 表示,形成的序列稱爲斐波那契數列。該數列由 01 開始,後面的每一項數字都是前面兩項數字的和。也就是:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

給定 N,計算 F(N)

示例 1:

輸入:2
輸出:1
解釋:F(2) = F(1) + F(0) = 1 + 0 = 1.

示例 2:

輸入:3
輸出:2
解釋:F(3) = F(2) + F(1) = 1 + 1 = 2.

示例 3:

輸入:4
輸出:3
解釋:F(4) = F(3) + F(2) = 2 + 1 = 3.

代碼

遞歸

    public int fib(int N) {
        if(N < 2) return N;
        return fib(N - 1) + fib(N - 2);
    }

動態規劃

    public int fib(int N) {
        //dp[N] = dp[N - 1] + dp[N - 2];
        int[] dp = new int[N + 1];
        if(N == 0) return 0;
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2; i <= N; i ++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[N];
    }

遍歷

    public int fib(int N) {
        if(N < 2) return N;
        int a = 0;
        int b = 1;
        int c = 0;
        for(int i = 2; i <= N; i ++){
            c = a + b;
            a = b;
            b = c;
        }
        return c;    
    }


【動態規劃】climbing-stairs(爬樓梯)

假設你正在爬樓梯。需要 n 階你才能到達樓頂。

每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是一個正整數。

示例 1:

輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1.  1 階 + 1 階
2.  2 階

示例 2:

輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1.  1 階 + 1 階 + 1 階
2.  1 階 + 2 階
3.  2 階 + 1 階

代碼

class Solution {
    public int climbStairs(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i ++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}


【動態規劃】triangle(三角形最小路徑和)

給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。

例如,給定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

自頂向下的最小路徑和爲 11(即,2 + 3 + 5 + 1 = 11)。

說明:

如果你可以只使用 O(n) 的額外空間(n 爲三角形的總行數)來解決這個問題,那麼你的算法會很加分。

代碼

二維數組

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        //dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j)
        int m = triangle.size();
        int[][] dp = new int[m][triangle.get(m - 1).size()];
        for (int i = 0; i < triangle.get(m - 1).size(); i ++) {
            dp[m - 1][i] = triangle.get(m - 1).get(i);
        }
        
        for (int i = m - 2; i >= 0; i --) {
            for (int j = 0; j < triangle.get(i).size(); j ++) {
                dp[i][j] = Math.min(dp[i + 1][j], 
                                    dp[i + 1][j + 1]) + triangle.get(i).get(j);   
            }
        }
        return dp[0][0];
    }
}

一維數組

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        //dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);   
        int m = triangle.size();
        int n = triangle.get(m - 1).size();
        int[] dp = new int[n];
        for (int i = 0; i < n; i ++) {
            dp[i] = triangle.get(m - 1).get(i);
        }
        
        for (int i = m - 2; i >= 0; i --) {
            for (int j = 0; j < triangle.get(i).size(); j ++) {
                dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);   
            }
        }
        return dp[0];
    }
}


【動態規劃】maximum-product-subarray(乘積最大子序列)

給定一個整數數組 nums ,找出一個序列中乘積最大的連續子序列(該序列至少包含一個數)。

示例 1:

輸入: [2,3,-2,4]
輸出: 6
解釋: 子數組 [2,3] 有最大乘積 6。

示例 2:

輸入: [-2,0,-1]
輸出: 0
解釋: 結果不能爲 2, 因爲 [-2,-1] 不是子數組。

代碼

解法1

class Solution {
    public int maxProduct(int[] nums) {
        int[][] dp = new int[2][2];
        dp[0][1] = nums[0];//最小值
        dp[0][0] = nums[0];//最大值
        int res = nums[0];
        for (int i = 1; i < nums.length; i ++) {
            int x = i % 2;
            int y = (i - 1) % 2;
            dp[x][0] = Math.max(Math.max(dp[y][0] * nums[i], dp[y][1] * nums[i]), nums[i]);
            dp[x][1] = Math.min(Math.min(dp[y][0] * nums[i], dp[y][1] * nums[i]), nums[i]);
            res = Math.max(res, dp[x][0]);
        }
        return res;
    }
}

解法2

class Solution {
    public int maxProduct(int[] nums) {
        int curMin = nums[0];//最小值
        int curMax = nums[0];//最大值
        int res = nums[0];
        for (int i = 1; i < nums.length; i ++) {
            int tmpMax = curMax * nums[i];
            int tmpMin = curMin * nums[i];
            curMin = Math.min(Math.min(tmpMax, tmpMin), nums[i]);
            curMax = Math.max(Math.max(tmpMax, tmpMin), nums[i]);
            res = Math.max(res, curMax);
        }
        return res;
    }
}


【動態規劃】買賣股票的最佳時機

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

如果你最多隻允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。

注意你不能在買入股票前賣出股票。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
     注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格。

示例 2:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

代碼

循環

class Solution {
    public int maxProfit(int[] prices) {
        int tmp = 0;
        int max = 0;
        for (int i = 1; i < prices.length; i ++) {
            int diff = prices[i] - prices[i - 1]; 
            //連續差值和 如 1 5 3 6 -- (6 - 1) = (5 - 1) + (3 - 5) + (6 - 3)
            tmp = tmp + diff > 0 ? tmp + diff : 0;
            max = Math.max(max, tmp);
        }
        return max;
    }
}

動態規劃

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) return 0;
        int res = 0;
        int[][] profit = new int[prices.length][3];
        profit[0][0] = 0;//沒有買入股票
        profit[0][1] = -prices[0];//買入股票沒有賣出
        profit[0][2] = 0;//之前買過股票,現在賣了
        
        for (int i = 1; i < prices.length; i ++) {
            profit[i][0] = profit[i - 1][0];
            profit[i][1] = Math.max(profit[i - 1][1], profit[i - 1][0] - prices[i]);//前一天買入股票,或者之前沒有股票今天剛買
            profit[i][2] = profit[i - 1][1] + prices[i];
            res = Math.max(Math.max(res, profit[i][0]), Math.max(profit[i][1], profit[i][2]));
        }
        return res;
    }
}


【動態規劃】買賣股票的最佳時機 III

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。

注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能獲得利潤 = 3-0 = 3 。
     隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能獲得利潤 = 4-1 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。   
     因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1] 
輸出: 0 
解釋: 在這個情況下, 沒有交易完成, 所以最大利潤爲 0。

代碼

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) return 0;
        int res = 0;
        //第一位代表天數,第二位代表交易次數,第三位0代表不持有股票,1代表持有股票
        int[][][] profit = new int[prices.length][3][2];
        
        for (int i = 0; i < 3; i ++) {
            //初始化第1天的數據
            profit[0][i][0] = 0;
            profit[0][i][1] = -prices[0];
        }
        
        for (int i = 1; i < prices.length; i ++) {
            for (int j = 0; j < 3; j ++) {
                //不持有股票分兩種情況
                //1:前一天持有,今天賣了 profit[i - 1][j - 1][1] + prices[i] 如果操作數爲0則不存在本情況
                //2: 前一天就不持有,今天無操作 profit[i - 1][j][0]
                profit[i][j][0] = j != 0 ? Math.max(profit[i - 1][j][0], profit[i - 1][j - 1][1] + prices[i]) : profit[i - 1][j][0];
                //持有股票分兩種情況
                //1: 前一天持有,今天無操作 profit[i - 1][j][1]
                //2:前一天不持有,今天買入 profit[i - 1][j][0] - prices[i]
                profit[i][j][1] = Math.max(profit[i - 1][j][1], profit[i - 1][j][0] - prices[i]);      
                res = Math.max(res, profit[i][j][0]);
            }

        }   
        return res;
    }
}


【動態規劃】longest-increasing-subsequence(最長上升子序列)

給定一個無序的整數數組,找到其中最長上升子序列的長度。

示例:

輸入: [10,9,2,5,3,7,101,18]
輸出: 4 
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。

說明:

  • 可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
  • 你算法的時間複雜度應該爲 O(n2) 。

進階: 你能將算法的時間複雜度降低到 O(n log n) 嗎?

代碼

動態規劃

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int res = 1;
        for (int i = 1; i < n; i ++) {    
            for (int j = 0; j < i; j ++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
                }
            }
            res = Math.max(res, dp[i]);
        }  
        return res;
    }
}

二分

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        List<Integer> lis = new ArrayList<>();
        lis.add(nums[0]);
        for (int i = 1; i < nums.length; i ++) {
            int left = 0;
            int right = lis.size() - 1;
            int it = -1;
            while (left <= right) {
                int mid = (left + right) / 2;
                if (lis.get(mid) >= nums[i]) {
                    it = mid;
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
            if (it == -1) {
                lis.add(nums[i]);
            } else {
                lis.set(it, nums[i]);
            }
        }
        return lis.size();
    }
}


【動態規劃】coin-change(零錢兌換)

給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1

示例 1:

輸入: coins = [1, 2, 5], amount = 11
輸出: 3 
解釋: 11 = 5 + 5 + 1

示例 2:

輸入: coins = [2], amount = 3
輸出: -1

說明:
你可以認爲每種硬幣的數量是無限的。

代碼

class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[max];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i < max; i ++) {
            for (int j = 0; j < coins.length; j ++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}


【動態規劃】edit-distance(編輯距離)

給定兩個單詞 word1word2,計算出將 word1 轉換成 word2 所使用的最少操作數 。

你可以對一個單詞進行如下三種操作:

  1. 插入一個字符
  2. 刪除一個字符
  3. 替換一個字符

示例 1:

輸入: word1 = "horse", word2 = "ros"
輸出: 3
解釋: 
horse -> rorse (將 'h' 替換爲 'r')
rorse -> rose (刪除 'r')
rose -> ros (刪除 'e')

示例 2:

輸入: word1 = "intention", word2 = "execution"
輸出: 5
解釋: 
intention -> inention (刪除 't')
inention -> enention (將 'i' 替換爲 'e')
enention -> exention (將 'n' 替換爲 'x')
exention -> exection (將 'n' 替換爲 'c')
exection -> execution (插入 'u')

代碼

class Solution {
    public int minDistance(String word1, String word2) {
        //dp[i][j] word1的前i個字符替換到word2的前j個字符最少需要的步數
        int m = word1.length();
        int n = word2.length();
        int[][] dp = new int[m + 1][n + 1];
        
        for (int i = 0; i < m + 1; i ++) dp[i][0] = i;//word2長度爲0 逐個刪除
        for (int i = 0; i < n + 1; i ++) dp[0][i] = i;//word1長度爲0 逐個添加
        
        for (int i = 1; i < m + 1; i ++) {
            for (int j = 1; j < n + 1; j ++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    //dp[i - 1][j]代表word1插入一個字符
                    //dp[i][j - 1]代表word1刪除一個字符
                    //dp[i - 1][j - 1]代表word1替換一個字符
                    dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                }
            }
        }
        return dp[m][n];
    }
}


【並查集】代碼模板

public class UnionFind {

    /**
     * 根數組 i = roots[j]代表j的根是i
     * 規定根相同的元素屬於同一集合
     */
    private int[] roots;
    /**
     * 並查集的深度(從0開始)
     */
    private int[] rank;

    /**
     * 初始化根數組,每個元素的根爲自己
     * @param N
     */
    public UnionFind(int N) {
        roots = new int[N];
        for (int i = 0; i < N; i++) {
            roots[i] = i;
        }
    }

    /**
     * 判斷兩個元素是否屬於同一個集合,即根元素是否相同
     * @param p
     * @param q
     * @return
     */
    public boolean connected(int p, int q) {
        return findRoot(p) == findRoot(q);
    }

    /**
     * 將兩個集合合併爲1個集合
     * @param p
     * @param q
     */
    public void union(int p, int q) {
        int qroot = findRoot(q);
        int proot = findRoot(p);
        if (qroot != proot) {
            //優化並查集,減少並查集的深度
            if (rank[proot] > rank[qroot]) {
                roots[qroot] = proot;
            } else if (rank[proot] < rank[qroot]) {
                roots[proot] = qroot;
            } else {
                roots[proot] = qroot;
                rank[qroot] += 1;
            }
        }
    }

    /**
     * 尋找指定元素的根元素
     * @param i
     * @return
     */
    private int findRoot(int i) {
        int root = i;
        while (root != roots[root]) {
            root = roots[root];
        }
        //優化並查集,將元素直接指向根元素,壓縮路徑
        while (i != roots[i]) {
            int tmp = roots[i];
            roots[i] = root;
            i = tmp;
        }
        return root;
    }
}



【並查集】number-of-islands(島嶼數量)

給定一個由 '1'(陸地)和 '0'(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,並且它是通過水平方向或垂直方向上相鄰的陸地連接而成的。你可以假設網格的四個邊均被水包圍。

示例 1:

輸入:
11110
11010
11000
00000

輸出: 1

示例 2:

輸入:
11000
11000
00100
00011

輸出: 3

代碼

DFS

class Solution {
    int count = 0;
    int[] dx = {-1, 1, 0, 0};
    int[] dy = {0, 0, -1, 1};
    public int numIslands(char[][] grid) {
        for (int i = 0; i < grid.length; i ++) {
            for (int j = 0; j < grid[0].length; j ++) { 
                if (grid[i][j] == '1') {
                    count ++;
                    dfs(grid, i, j);
                }
            }
        }
        return count;
    }
    
    private void dfs(char[][] grid, int i, int j) {
        if (valid(grid, i, j)) {
            grid[i][j] = '0';
            for (int k = 0; k < 4; k ++) {
                dfs(grid, i + dx[k], j + dy[k]);
            }
        }
    }
    
    private boolean valid(char[][] grid, int i, int j) {
        return i >= 0 && i < grid.length && j >=0 && j < grid[0].length && grid[i][j] == '1';
    }
}

並查集

class Solution {
    
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) return 0;
        UnionFind uf = new UnionFind(grid);
        int[] dx = {-1, 1, 0, 0};
        int[] dy = {0, 0, -1, 1};
        int m = grid.length;
        int n = grid[0].length;
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                if (grid[i][j] == '0') {
                    continue;
                }
                for (int k = 0; k < 4; k ++) {
                    int a = i + dx[k];
                    int b = j + dy[k];
                    if (valid(grid, a, b)) {
                        uf.union(i * n + j, a * n + b);
                    }
                }
            }
        }
        return uf.getCount();
    }

    private boolean valid(char[][] grid, int i, int j) {
        return i >= 0 && i < grid.length && j >=0 && j < grid[0].length && grid[i][j] == '1';
    }
}

class UnionFind {
    private int count;
    private int[] roots;
    private int[] rank;
    
    public UnionFind(char[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        count = 0;
        roots = new int[m * n];
        rank = new int[m * n];
        Arrays.fill(roots, -1);
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                if (grid[i][j] == '1') {
                    roots[i * n + j] = i * n + j;
                    count += 1;
                }
            }
        }
    }
    
    public int getCount() {
        return this.count;
    }
    
    public void union(int p, int q) {
        int proot = find(p);
        int qroot = find(q);
        if (qroot != proot) {
            if (rank[proot] > rank[qroot]) {
                roots[qroot] = proot;
            } else if (rank[proot] < rank[qroot]) {
                roots[proot] = qroot;
            } else {
                roots[proot] = qroot;
                rank[qroot] += 1;
            }
            count -= 1;
        }
    }
    
    private int find(int i) {
        int root = i;
        while (root != roots[root]) {
            root = roots[root];
        }
        while (i != roots[i]) {
            int tmp = roots[i];
            roots[i] = root;
            i = tmp;
        }
        return root;
    }
}


【並查集】friend-circles(朋友圈)

班上有 N 名學生。其中有些人是朋友,有些則不是。他們的友誼具有是傳遞性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那麼我們可以認爲 A 也是 C 的朋友。所謂的朋友圈,是指所有朋友的集合。

給定一個 N * N 的矩陣 M,表示班級中學生之間的朋友關係。如果M[i][j] = 1,表示已知第 i 個和 j 個學生互爲朋友關係,否則爲不知道。你必須輸出所有學生中的已知的朋友圈總數。

示例 1:

輸入: 
[[1,1,0],
 [1,1,0],
 [0,0,1]]
輸出: 2 
說明:已知學生0和學生1互爲朋友,他們在一個朋友圈。
第2個學生自己在一個朋友圈。所以返回2。

示例 2:

輸入: 
[[1,1,0],
 [1,1,1],
 [0,1,1]]
輸出: 1
說明:已知學生0和學生1互爲朋友,學生1和學生2互爲朋友,所以學生0和學生2也是朋友,所以他們三個在一個朋友圈,返回1。

注意:

  1. N 在[1,200]的範圍內。
  2. 對於所有學生,有M[i][i] = 1
  3. 如果有M[i][j] = 1,則有M[j][i]= 1

代碼

DFS

class Solution {
    int count = 0;
    public int findCircleNum(int[][] M) {
        int[] mark = new int[M.length];//用於存儲朋友圈路徑
        for (int i = 0; i < M.length; i ++) {
            if (mark[i] == 0) {
                //0代表i沒有加入到一個朋友圈,從該點開始,dfs找出他的所有朋友
                count ++;
                dfs(M, mark, i);//執行一次,構建一條朋友圈
            }
        }
        return count;
    }
    private void dfs(int[][] M, int[] mark, int i) {
        mark[i] = 1;//將i放到朋友圈
        for (int j = 0; j < M[0].length; j ++) {
            if (M[i][j] == 1 && mark[j] == 0) {
                //如果i和j是朋友,但是j沒有加入該朋友圈,dfs去構建朋友圈
                dfs(M, mark, j);
            }
        }
    }
}

並查集

class Solution {
    public int findCircleNum(int[][] M) {
        UnionFind uf = new UnionFind(M);
        int m = M.length;
        int n = M[0].length;
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) { 
                if (M[i][j] == 1) {
                    //這裏太暴力了,暫時沒有優化,速度那是慢的一匹
                    //上面的dfs是優化過的,換了個思路,當然也可以按照下面暴力求解
                    for (int k = 0; k < m; k ++) {
                        if (M[k][j] == 1) {
                            uf.union(i * n + j, k * n + j);   
                        }
                    }
                    for (int k = 0; k < n; k ++) {
                        if (M[i][k] == 1) {
                            uf.union(i * n + j, i * n + k);
                        }
                    }
                }
            }
        }
        return uf.getCount();
    }
}

class UnionFind {
    private int count;
    private int[] roots;
    private int[] rank;
    
    public UnionFind(int[][] M) {
        int m = M.length;
        int n = M[0].length;
        count = 0;
        roots = new int[m * n];
        rank = new int[m * n];
        Arrays.fill(roots, -1);
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                if (M[i][j] == 1) {
                    roots[i * n + j] = i * n + j;
                    count += 1;
                }
            }
        }
    }
    
    public int getCount() {
        return this.count;
    }
    
    public void union(int p, int q) {
        int proot = find(p);
        int qroot = find(q);
        if (qroot != proot) {
            if (rank[proot] > rank[qroot]) {
                roots[qroot] = proot;
            } else if (rank[proot] < rank[qroot]) {
                roots[proot] = qroot;
            } else {
                roots[proot] = qroot;
                rank[qroot] += 1;
            }
            count -= 1;
        }
    }
    
    private int find(int i) {
        int root = i;
        while (root != roots[root]) {
            root = roots[root];
        }
        while (i != roots[i]) {
            int tmp = roots[i];
            roots[i] = root;
            i = tmp;
        }
        return root;
    }
}


【LRU】lru-cache(LRU緩存機制)

運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制。它應該支持以下操作: 獲取數據 get 和 寫入數據 put

獲取數據 get(key) - 如果密鑰 (key) 存在於緩存中,則獲取密鑰的值(總是正數),否則返回 -1。
寫入數據 put(key, value) - 如果密鑰不存在,則寫入其數據值。當緩存容量達到上限時,它應該在寫入新數據之前刪除最近最少使用的數據值,從而爲新的數據值留出空間。

進階:

你是否可以在 O(1) 時間複雜度內完成這兩種操作?

示例:

LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 該操作會使得密鑰 2 作廢
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 該操作會使得密鑰 1 作廢
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

代碼

class LRUCache {
    
    private LinkedHashMap<Integer, Integer> map;
    private int remain;
    public LRUCache(int capacity) {
        this.map = new LinkedHashMap<>(capacity);
        this.remain = capacity;
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        int v = map.remove(key);
        map.put(key, v);
        return v;
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            map.remove(key);
        } else {
            if (this.remain > 0) this.remain --;
            else removeOldestEntry();
        }
        map.put(key, value);
    }
    
    private void removeOldestEntry() {
        Iterator<Integer> it = this.map.keySet().iterator();
        int oldestKey = -1;
        if (it.hasNext()) {
            oldestKey = it.next();
        }
        map.remove(oldestKey);
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章