leetcode算法总结(方法论篇)

1、特征题

1、回文系列

方法论:中心拓展法

因为回文串是中心对称的,我们可以先枚举子串的中心,然后从中心处向两边探测,直到发现两端字符不相等或者到达字符串边缘。

  • s长度为 奇数,中心是单个字符,以 s[i]为中心向两边扩展
  • s长度为 偶数,中心是两个字符,以 s[i]、s[i+1]为中心向两边扩展

案例:

647. 回文子串

5. 最长回文子串

2、重复或不重复

方法论:用数组、map、bitmap记录,根据实际场景记录次数或者最后位置

案例:

3. 无重复字符的最长子串

3、数组合并

方法论:双指针

案例:

21. 合并两个有序链表

23. 合并K个升序链表

4、环

4.1 判断

方法论:快慢指针,是否相遇(快指针两步,慢指针一步)

案例:

141. 环形链表

142. 环形链表 II

4.2 增删

5、旋转数组

方法论:二分法

此题运用二分法的特殊变种情况来区分:

if nums[mid] == target
if nums[left] <= nums[mid],mid的左侧是单调递增区间。注意,当nums中元素较少时,可能left和mid的下标值相同,因此这里的 nums[left] 也可能等于 nums[mid]。
if nums[left] <= target && target < nums[mid]
right = mid - 1
else
left = mid + 1
else /*if nums[left] > nums[mid]*/,mid的左侧不是单调递增区间,说明右侧是单调递增区间
if nums[mid] < target && target <= nums[right]
left = mid + 1
else
right = mid - 1

案例:

33. 搜索旋转排序数组

6、第k大、前k大

方法论:小顶堆

堆,又称优先级队列,在逻辑上可以视为一棵完全二叉树,且满足每个节点的值小于等于(小根堆)其左右孩子节点的值。

public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> queue = new PriorityQueue<>();//系统默认即为小根堆
    int i = 0;
    for(;i < k;i++)
        queue.add(nums[i]);//前k个元素建堆,也可以用offer
    for(;i < nums.length;i++)
        if(queue.peek()<nums[i]){//peek拿出堆顶元素比大小
            queue.poll();
            queue.add(nums[i]);//java中没有replace操作,拆分成两步
        }
    return queue.peek();
}

public int findKthLargest(int[] nums, int k) {
    // nums[0...k-1]建小根堆,从最后一个节点的父节点开始往前heapify,其余基本与上个版本一致
    for(int j = (k-2)/2;j >= 0;j--)
        heapify(nums,j,k);
    for(int j = k;j < nums.length;++j){
        if(nums[0] >= nums[j])
            continue;
        swap(nums,0,j);
        heapify(nums,0,k);
    }
    return nums[0];
}
// 调整大小为size的堆arr中的i号元素
public void heapify(int[] arr,int i,int size){
    int left = i*2+1;
    while(left < size){
        int small = left+1 < size && arr[left] > arr[left+1]? left+1: left;
        if(arr[i]<=arr[small])
            break;
        swap(arr,i,small);
        i = small;
        left = i*2+1;
    }
}
public void swap(int[] arr,int a,int b){
    arr[a] ^= arr[b];
    arr[b] ^= arr[a];
    arr[a] ^= arr[b];
}

案例:

215. 数组中的第K个最大元素

347. 前 K 个高频元素

7、成对匹配(括号等)

方法论:栈、数组;匹配到了出栈

案例:

20. 有效的括号

8、两两匹配

方法论:kmp算法

9、子串问题

方法论:双指针,再结合具体问题动态规划等

案例:

560.和为 K 的子数组
53.最大子数组和

备注:子序列是非连续的,子串是连续的
 

2、通用题

1、找出前后更大/更小

方法论:单调栈,记录前面最大的

案例:

42. 接雨水

84. 柱状图中最大的矩形

155. 最小栈

394. 字符串解码

739. 每日温度

2、矩阵遍历

void dfs(int[][] grid, int r, int c) {
    // 判断 base case
    if (!inArea(grid, r, c)) {
        return;
    }
    // 如果这个格子不是岛屿,直接返回
    if (grid[r][c] != 1) {
        return;
    }
    grid[r][c] = 2; // 将格子标记为「已遍历过」
    
    // 访问上、下、左、右四个相邻结点
    dfs(grid, r - 1, c);
    dfs(grid, r + 1, c);
    dfs(grid, r, c - 1);
    dfs(grid, r, c + 1);
}

// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
    return 0 <= r && r < grid.length 
        	&& 0 <= c && c < grid[0].length;
}

案例:

85. 最大矩形

200. 岛屿数量

3、链表变更

方法论:找到交换节点的前节点、指针

//1、备份
//2、先解除
//3、后插入
ListNode tmp = pre.next;
pre.next = post.next;
post.next = post.next.next;
pre.next.next = tmp;

案例:

删除链表的倒数第 N 个结点

237. 删除链表中的节点

4、全排列和子集

方法论:回溯

class Solution {
    private int[] nums;
    private List<Integer> path;
    private boolean[] onPath;
    private final List<List<Integer>> ans = new ArrayList<>();

    public List<List<Integer>> permute(int[] nums) {
        this.nums = nums;
        path = Arrays.asList(new Integer[nums.length]);
        onPath = new boolean[nums.length];
        dfs(0);
        return ans;
    }

    private void dfs(int i) {
        if (i == nums.length) {
            ans.add(new ArrayList<>(path));
            return;
        }
        for (int j = 0; j < nums.length; ++j) {
            if (!onPath[j]) {
                path.set(i, nums[j]);
                onPath[j] = true;
                dfs(i + 1);
                onPath[j] = false; // 恢复现场
            }
        }
    }
}

/**
 * 循环枚举
 */
public static List<List<Integer>> enumerate(int[] nums) {
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    res.add(new ArrayList<Integer>());
    for (Integer n : nums) {
        int size = res.size();
        for (int i = 0; i < size; i++) {
            List<Integer> newSub = new ArrayList<Integer>(res.get(i));
            newSub.add(n);
            res.add(newSub);
        }
    }
    return res;
}

案例:

39. 组合总和

46. 全排列

78. 子集

5、树

方法论:

  1. 先中后:递归
  2. 层序遍历:队列
  3. 深度搜索:

案例:

105. 从前序与中序遍历序列构造二叉树

98. 验证二叉搜索树

236. 二叉树的最近公共祖先

114. 二叉树展开为链表

6、动态规划

常见的判断:依赖去前一步的结果进行下一步

方法论:存储并重复利用己经计算的结果

6.1 一维

案例:

53. 最大子数组和

300. 最长递增子序列

53. 最大子数组和

62. 不同路径

279. 完全平方数

494. 目标和

6.2 二维

结合二维数组使用,常用图、或者多个条件的状态

案例:

64.最小路径和
1143.最长公共子序列

7、贪心

方法论:以最小成本倒推或者前进

案例:

55. 跳跃游戏

附录

java常用结构

善用二维数组

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

队列

Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
TreeNode node = queue.poll();

Stack<Character> stack = new Stack<>();
stack.pop();
stack.push(c);

链表

public class ListNode {
    int val;
    ListNode next;
    //双链表多一个pre节点
}

用数组实现栈、队列

用栈实现队列

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值