力扣算法模板 & 刷题顺序
...大约 8 分钟lc
算法模板
光看模板肯定看不懂的,刷几道题就明白了
- 部分参考了lambdadong的算法小炒
二分查找
int binarySearch(int[] nums, int target) {
    int left = 0, right = ...;
    while(...) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}
↖️↗️双指针
快慢指针的常见算法
判定链表中是否含有环
boolean hasCycle(ListNode head) {
    while (head != null)
        head = head.next;
    return false;
}
boolean hasCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        
        if (fast == slow) return true;
    }
    return false;
}
已知链表中含有环,返回这个环的起始位置
ListNode detectCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) break;
    }
    // 上面的代码类似 hasCycle 函数
    if (fast == null || fast.next == null) {
        // fast 遇到空指针说明没有环
        return null;
    }
    slow = head;
    while (slow != fast) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}
寻找链表的中点
while (fast != null && fast.next != null) {
    fast = fast.next.next;
    slow = slow.next;
}
// slow 就在中间位置
return slow;
寻找链表的倒数第 k 个元素
ListNode slow, fast;
slow = fast = head;
while (k-- > 0) 
    fast = fast.next;
while (fast != null) {
    slow = slow.next;
    fast = fast.next;
}
return slow;
左右指针的常用算法
二分查找
int binarySearch(int[] nums, int target) {
    int left = 0; 
    int right = nums.length - 1;
    while(left <= right) {
        int mid = (right + left) / 2;
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; 
        else if (nums[mid] > target)
            right = mid - 1;
    }
    return -1;
}
两数之和
int[] twoSum(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left < right) {
        int sum = nums[left] + nums[right];
        if (sum == target) {
            // 题目要求的索引是从 1 开始的
            return new int[]{left + 1, right + 1};
        } else if (sum < target) {
            left++; // 让 sum 大一点
        } else if (sum > target) {
            right--; // 让 sum 小一点
        }
    }
    return new int[]{-1, -1};
}
反转数组
void reverse(int[] nums) {
    int left = 0;
    int right = nums.length - 1;
    while (left < right) {
        // swap(nums[left], nums[right])
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
        left++; right--;
    }
}
滑动窗口算法
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    int left = 0, right = 0;
    int valid = 0;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...
        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);
        /********************/
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}
↩️ 回溯
回溯的大致模板就是以下,掌握三个核心点就行:
- 递归函数
- 递归里肯定有一个if(至少一个)
- 递归里或者外面有一个for循环 完毕,剩下的自己刷题领悟,下面是模板
List<List<Integer>> res = new LinkedList<>();
/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
    // 记录「路径」
    LinkedList<Integer> track = new LinkedList<>();
    backtrack(nums, track);
    return res;
}
// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
    // 触发结束条件
    if (track.size() == nums.length) {
        res.add(new LinkedList(track));
        return;
    }
    
    for (int i = 0; i < nums.length; i++) {
        // 排除不合法的选择
        if (track.contains(nums[i]))
            continue;
        // 做选择
        track.add(nums[i]);
        // 进入下一层决策树
        backtrack(nums, track);
        // 取消选择
        track.removeLast();
    }
}
例题:
🔝 贪心
贪心的3个核心点:
- for循环
- Math.max
- 数组中的当前元素和前一个元素相关(至于为什么刷完题就知道了)
bool canJump(vector<int>& nums) {
    int n = nums.size();
    int farthest = 0;
    for (int i = 0; i < n - 1; i++) {
        // 不断计算能跳到的最远距离
        farthest = max(farthest, i + nums[i]);
        // 可能碰到了 0,卡住跳不动了
        if (farthest <= i) return false;
    }
    return farthest >= n - 1;
}
贪心就是通过局部最优求全局最优,最两道题就明白了
🛣️ 动态规划
动态规划是我最怕的题目之一,主要是要思考状态转移方程
# 初始化 base case
dp[0][0][...] = base
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)
推荐两道题去理解动态规划:
更进阶的动态规划可能还要考虑更多情况
- 考虑负数情况:乘积最大子数组
🌈 分治
@todo
🔎 并查集
@todo
刷题顺序
二叉树
- 掌握二叉树递归与非递归遍历
- 理解 DFS 前序遍历与分治法
- 理解 BFS 层次遍历
精选:
链表
- null/nil异常处理
- dummy node哑巴节点
- 快慢指针
- 插入一个节点到排序链表
- 从一个链表中移除一个节点
- 翻转链表
- 合并两个链表
- 找到链表的中间节点
字符串
需要考虑的问题:
- 要不要使用库函数
- 双指针法
- 反转系列
- KMP
动态规划
最长递增子序列 ➡️ 单词拆分 ➡️ 编辑距离
矩阵DP
序列
两个序列的DP
背包 & 零钱兑换
回溯
滑动窗口
位运算
分治
@todo
贪心算法
@todo
参考刷题顺序的仓库
- 算法模板:https://greyireland.gitbook.io/algorithm-pattern/shu-ju-jie-gou-pian/binary_tree
- labuladong 的算法:https://labuladong.gitee.io/algo/
- 代码随想录:https://github.com/youngyangyang04/leetcode-master
- 小浩算法:https://www.geekxh.com/
力扣常用方法封装
快慢指针找中间位置
private ListNode endOfFirstHalf(ListNode head) {
    ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
    return slow;
}
反转链表
这里用的是力扣官方的代码,我的声明的变量稍微有点多
private ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}
后序遍历
public void postorder(TreeNode root, List<Integer> res) {
    if (root == null) {
        return;
    }
    postorder(root.left, res);
    postorder(root.right, res);
    res.add(root.val);
}
计算最大深度
private int dfs(TreeNode node) {
    if (node == null) {
        return 0;
    }
    int l = dfs(node.left) + 1;
    int r = dfs(node.right) + 1;
    return Math.max(l, r);
}
判断是否是平衡二叉树
public boolean isBalanced(TreeNode root) {
        if (root == null) return true;
        return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
    }
如果要用HashMap统计个数
HashMap<Integer, Integer> map = new HashMap<>();
// 遍历数组,统计每个数字出现的次数
for (int num : nums) {
    int count = map.getOrDefault(num, 0);
    map.put(num, count + 1);
}