实战绝佳的LeetCode刷题方法

        学生时代找工作前就刷过算法题,不过现在回看当初,单纯根据LeetCode上的分类:数组、字符串、动态规划这样的分类去刷,确实略显盲目。如何才能更高效、更聚焦地刷出最大价值呢?这次换工作,我前后刷了不到200题,面试多家公司,依旧遇到近半未做过的题,但基本都AC,特将心得记录在此,与君共勉。

        分享一个以结果为导向的刷题网站:CodeTop 面试题目总结 。根据你想去的公司,去刷他们最高频的题目吧。亲测50%有效。

        刷题核心主要3点:数据结构+算法+模板代码,对应到10余种常见题目类型,每种类型刷10题左右,深度理解一些经典题解,基本这一类的题面试中都不会再感棘手。下面就核心要点和题目类型简要梳理,随时使用。

数据结构

        常见数据结构诸如数组Array、链表Node、列表List、树Tree、字典Map、集合Set等,在对应语言中的写法及常用方法必须熟记。延伸一点包括栈Stack 、堆Heap、队列Queue等也必须熟悉。

        比如Java中将一个数组准递增排序,初始化一个优先级队列(按照大顶堆的形式),栈的出入等,都需要熟练掌握。

        特别对于字符串String的一些操作,包括翻转reverse、子串substring、前缀startsWith、分片split等等也会常考,需要熟练。

        以前这些知识都需要大家去各种地方搜集,如今随着大模型的兴起,对于数据结构的快速熟悉,大模型能起到事半功倍的效果。例如上面这些数据结构的基础逻辑、常用方法、模板代码等大模型都能快速回答,效果极佳。

算法

  1. 排序:快排、归并排序、堆排序,必须完全掌握,其他如冒泡排序、桶排序、插入排序等尽可能掌握。练手:. - 力扣(LeetCode)
  2. 二分查找:. - 力扣(LeetCode),注意有单调性就可以用二分,而不一定是明显排序过的数组,练习:. - 力扣(LeetCode)
  3. 二叉树:前序、中序、后序、层序遍历,递归&非递归,必须掌握。练手:. - 力扣(LeetCode) 及相似题目,. - 力扣(LeetCode), 大部分树相关的题目都可以使用DFS解决,练手:. - 力扣(LeetCode)
  4. 链表翻转:. - 力扣(LeetCode)
  5. DFS、BFS:DFS的递归和stack方式,BFS的队列queue方式, . - 力扣(LeetCode)
  6. 递归+剪枝:. - 力扣(LeetCode)
  7. 双指针:. - 力扣(LeetCode)
  8. 单调栈:. - 力扣(LeetCode),困难:. - 力扣(LeetCode)
  9. 拓扑排序:. - 力扣(LeetCode)
  10. 动态规划:. - 力扣(LeetCode)

其他例如并查集、贪心、滑动窗口、排列组合、背包问题等都可以使用上面的基础算法来解决,可以不学,防止越学越乱。

除了算法以外,还有一些思维上的优秀解法

  1. 逆向思维:. - 力扣(LeetCode),最好的扔鸡蛋方式 . - 力扣(LeetCode)
  2. 预处理:. - 力扣(LeetCode). - 力扣(LeetCode)

模板代码

        对应上述的算法,每种都可以抽象出模板代码,大家也可以借用大模型给出模板代码。下面仅针对两类给出我的模板代码

  • 动态规划
// 子问题: 确定是一维DP还是二维DP,一般就是题目问的直接问题,部分难度较高的为中间流转态的DP,最终根据中间态算出结果
// 转移: 分条件确定dp[i]和dp[i-1]或dp[i+1]的关系,有时候因为下标从0开始容易乱,可以先用f(i)写清楚转移方程,再转换为真实下标的转移方程
// 边界:例如二维dp,通常是i或j其中一个动态,另一个固定为0或l-1之类的固定值,如 dp[i][0]=xx, dp[0][j]=yy。从常量开始遍历,计算出所有非常量的值
  • 排序
class Solution {

    public int[] sortArray(int[] nums) {
        // 参数校验
        if(null == nums || 1 >= nums.length) {
            return nums;
        }
        int l = nums.length, n = l;

        // 堆排序
        // heapSort(nums);

        // 快排:
        // quickSort(nums);

        // 归并
        // mergeSort(nums);

        // 桶排序
        bucketSort(nums);

        return nums;
    }

    public int[] mergeSort(int[] nums) {
        // 参数校验
        if(null == nums || 1 >= nums.length) {
            return nums;
        }
        int l = nums.length;

        // 归并排序:将当前区间一分为二,分别递归归并,然后使用插入排序,将子区间2的值逐个插入到子区间1中
        this.mergeSort(nums, 0, l-1);

        return nums;
    }

    private void mergeSort(int[] nums, int left, int right) {
        if(left >= right) {
            return;
        }

        int mid = left + (right - left)/2;
        // System.out.println("mergeSort: left: " + left + " right: " + right + " mid: " + mid);

        mergeSort(nums, left, mid);
        mergeSort(nums, mid+1, right);
        
        int i1 = left, i2 = mid+1, i = 0;

        int[] temp = new int[right - left + 1];
        while(i1 <= mid && i2 <= right) {
            if(nums[i1] < nums[i2]) {
                temp[i++] = nums[i1++];
            } else {
                temp[i++] = nums[i2++];
            }
        }
        while(i1 <= mid) {
            temp[i++] = nums[i1++];
        }
        while(i2 <= right) {
            temp[i++] = nums[i2++];
        }
        // System.out.println("temp i:" + i);
        // for(int k = 0; k < temp.length; k++) {
        //     System.out.print(temp[k] + ", ");
        // }
        // System.out.println();
        for(int k = 0; k < i; k++) {
            nums[left + k] = temp[k];
        }
    }

    public int[] quickSort(int[] nums) {
        // 参数校验
        if(null == nums || 1 >= nums.length) {
            return nums;
        }
        int l = nums.length;

        // 快排:选取一个比较支点 pivot ,当前区间排成 <=pivot pivot >pivot 的三段,再对这三段递归快排
        this.quick(nums, 0, l-1);

        return nums;
    }

    private void quick(int[] nums, int left, int right) {
        if(left >= right) {
            return;
        }

        int pivot = findPivot(nums, left, right);
        quick(nums, left, pivot - 1);
        quick(nums, pivot + 1, right);
    }

    private int findPivot(int[] nums, int left, int right) {
        int k = left;
        for(int i = left+1; i <= right; i++) {
            if(nums[i] > nums[left]) {
                continue;
            }
            swap(nums, ++k, i);
        }
        if(k != left) {
            swap(nums, k, left);
        }
        return k;
    }

    public int[] heapSort(int[] nums) {
        // 参数校验
        if(null == nums || 1 >= nums.length) {
            return nums;
        }
        int l = nums.length, n = l;

        // 堆排序:初始化堆
        initHeap(nums, n);

        // 从右往左遍历数组,每次将 i 和 0 位置的下标交换,直到 i== 0 。 
        // 每次交换后,i--, n-- . 此时仅堆顶元素不符合大顶堆,针对堆顶元素进行大顶堆化即可
        for(int i = l-1; i >= 0; i--) {
            swap(nums, 0, i);
            heaplify(nums, 0, i);
        }

        return nums;
    }

    // 初始化大顶堆
    private void initHeap(int[] nums, int n) {
        // 参数校验
        if(null == nums || n <= 1) {
            return;
        }

        // 从 i = n/2-1 开始到 i == 0,对于每个元素,进行大顶堆化
        for(int i = n/2-1; i >= 0; i--) {
            heaplify(nums, i, n);
        }
    }
    
    // 大顶堆化
    private void heaplify(int[] nums, int i, int n) {
        // 将 i 2i 2i+1 中最大的放在父节点,发生变化的子节点递归大顶堆化
        int leftChild = 2*i+1, rightChild = 2*i+2, last = i;
        if(leftChild < n && nums[leftChild] > nums[last]) {
            last = leftChild;
        }
        if(rightChild < n && nums[rightChild] > nums[last]) {
            last = rightChild;
        }
        if(last != i) {
            swap(nums, last, i);
            heaplify(nums, last, n);
        }
    }

    public void bucketSort(int[] nums) {
        // 参数校验
        if(null == nums || 1 >= nums.length) {
            return;
        }
        int l = nums.length, n = l;

        // 确定分桶数量 bucketNum , 
        int bucketNum = l;
        // 当前数的桶索引计算 calBucketIndex() , 数组中的数最好能均匀的分到每个桶中
        List<List<Integer>> bucketList = new ArrayList<>(bucketNum);
        for(int i = 0; i < bucketNum; i++) {
            bucketList.add(new ArrayList<Integer>());
        }
        for(int num : nums) {
            int index = calBucketIndex(num, -5 *10*10*10*10, 5 *10*10*10*10, bucketNum);
            bucketList.get(index).add(num);
        }        

        // 对于每个桶采取某种排序算法,例如快排
        int k = 0;
        for(List<Integer> list : bucketList) {
            int size = list.size();
            int[] arr = new int[size];
            for(int i = 0; i < size; i++) {
                arr[i] = list.get(i);
            }
            Arrays.sort(arr);
            for(int i = 0; i < size; i++) {
                nums[k++] = arr[i];
            }
        }
        // 分桶按序合并
    }

    private int calBucketIndex(int cur, int low, int high, int bucketNum) {
        int capacity = (high - low + 1) / bucketNum;
        capacity = Math.max(capacity, 1);
        return Math.min((cur - low + 1) / capacity, bucketNum - 1);
    }

    private void swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值