常用算法思路总结

      最近将剑指 Offer(专项突击版)刷了一遍,总结一下,最好结合图书来看,书里有很多总结,书中解法很经典,leetcode地址:

力扣

常见的算法方法

二分查找

双指针 滑动窗口

堆 优先级队列

单调栈

前缀树 字典树

前缀和+哈希

排序 快速排序 归并排序

并查集

拓扑排序

回溯法

动态规划

二分查找

根据题意 分析出 目标值target 以及 左边界left 和 右边界 right,计算mid=(left+right)/2;  计算当mid时 取值函数f(mid) ,比较f(mid)和target大小关系 来决定左右边界的移动。

函数返回时 一般有  f(mid) >target &&  f(mid-1) < target 此时mid是大于target的最小值;

一般左边界移动 left = mid+1 右边界 right = mid – 1;

关键在于 left right 以及f(mid)计算  以及函数返回时的逻辑处理

经典题目:

力扣

class Solution {

   public int minEatingSpeed(int[] piles, int h) {

        int left = 1;

        int right = Arrays.stream(piles).max().getAsInt();

        while (left <= right) {

            int mid = (left + right) >> 1;

            if (getTime(piles, mid) <= h) {

                if (mid == 1 || getTime(piles, mid-1) > h) {

                    return mid;

                }

                right = mid - 1;

            } else {

                left = mid + 1;

            }

        }

        return -1;

    }

    private int getTime(int[] piles, int speed) {

        int result = 0;

        for (int pile: piles) {

            result += (pile - 1) / speed + 1;

        }

        return result;

    }

}

双指针 滑动窗

第一种 前后双指针

左指针left从起始位置出发,右指针right从结束位置出发,根据条件来移动左右指针,计算函数f(left,right),终止条件一般是left<=right。一般用来解决排序数组子数组求和,回文串判断以及极值判断

经典题目

力扣

力扣

力扣

public int maxArea(int[] h) {

        int left = 0;

        int right = h.length - 1;

        int maxArea = 0;

        // 利用双指针 初始指向索引最小值和最大值

        // 矩形的面积是由双指针距离差*双指针对应的数组元素最小值决定的

        // 矩形面积可能增大的情况是 指针指向元素较小值发生变化

        // 尽管指针位置发生变化会使right-left减小 但是min(h[left], h[right])的增大可能会使总体面积而增大

        while (left <= right) {

            maxArea = Math.max(maxArea, (right - left) * Math.min(h[left], h[right]));

            if (h[left] < h[right]) {

                left++;

            } else {

                right--;

            }

        }

        return maxArea;

    }

第二种 滑动窗口

采用左右双指针  左右指针从其实位置出发,右指针不断向右 此时有区间[left,right] 定义区间函数 f(left, right) 如果此时f(left, right)已经符合题意,则已经找到可行解,此时尝试左指针右移,即此时区间缩小 计算此时f(left, right)是否符合题意,如果符合 则记录一个可行解,如果不满足,继续右指针右移,增大窗口,探索更多可行解。

力扣

class Solution {

    public int lengthOfLongestSubstring(String s) {

        int max = 0;

        Map<Character, Integer> map = new HashMap<>();

        int left = 0;

        int right = 0;

        for ( ; right < s.length(); right++) {

            map.put(s.charAt(right), map.getOrDefault(s.charAt(right), 0) + 1);

            while (!allLessThanOne(map) && left <= right) {

                map.put(s.charAt(left), map.getOrDefault(s.charAt(left), 0) - 1);

                left++;

            }

            max = Math.max(max, right - left + 1);

        }

        return max;

    }

    private boolean allLessThanOne(Map<Character, Integer> map) {

        return map.values().stream().filter(num -> num > 1).count() == 0;

    }

}

https://leetcode-cn.com/problems/wtcaE1/

class Solution {

    public int minSubArrayLen(int target, int[] nums) {

        int left = 0;

        int right = 0;

        int sum = 0;

        int min = Integer.MAX_VALUE;

        int total = Arrays.stream(nums).sum();

        if (total < target) return 0;

        for (right=0; right<nums.length; right++) {

            sum = sum + nums[right];

            while (sum >= target) {

                min = Math.min(min, right-left+1);

                sum = sum - nums[left];

                left++;

            }

        }

        return min;

    }

}

第三种 链表前后双指针 链表快慢双指针

力扣

https://leetcode-cn.com/problems/c32eOV/

堆 优先级队列

class Solution {

    public int[] topKFrequent(int[] nums, int k) {

        Map<Integer, Integer> map = new HashMap<>();

        for (int num: nums) {

            map.put(num, map.getOrDefault(num, 0) + 1);

        }

        PriorityQueue<Map.Entry<Integer, Integer>> queue =

                new PriorityQueue<>(Comparator.comparingInt(Map.Entry::getValue));

        for (Map.Entry<Integer, Integer> entry: map.entrySet()) {

            if (queue.size() < k) {

                queue.add(entry);

            } else {

                if (queue.peek().getValue() < entry.getValue()) {

                    queue.poll();

                    queue.add(entry);

                }

            }

        }

        int[] result = new int[queue.size()];

        int i = 0;

        while (!queue.isEmpty()) {

            result[i++] = queue.poll().getKey();

        }

        return result;

    }

}

堆 一般用来求解 K值问题,最小堆以及最大堆

单调栈

根据题意 需要保持栈中数据是排序的,给定序列,当某一个元素要入栈时,需要比较当前元素与栈顶元素的大小关系,一般需要将栈顶元素出栈之后,才能将当前元素继续加入栈中。

力扣

class Solution {

    public int[] dailyTemperatures(int[] temperatures) {

        int[] result = new int[temperatures.length];

        Stack<Integer> stack = new Stack<>();

        for (int i=0; i<temperatures.length; i++) {

            while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {

                int index = stack.pop();

                result[index] = i - index;

            }

            stack.push(i);

        }

        while (!stack.isEmpty()) {

            result[stack.pop()] = 0;

        }

        return result;

    }

}

前缀树 字典树

前缀树的构建以及前缀树的搜索,一般用于字符串的前缀搜索

力扣

public class Trie {

    static class TreeNode {

        TreeNode[] children = new TreeNode[26];

        boolean isWord;

    }

    TreeNode root;

    /**

     * Initialize your data structure here.

     */

    public Trie() {

        root = new TreeNode();

    }

    /**

     * Inserts a word into the trie.

     */

    public void insert(String word) {

        TreeNode node = root;

        for (char ch : word.toCharArray()) {

            if (node.children[ch - 'a'] == null) {

                node.children[ch - 'a'] = new TreeNode();

            }

            node = node.children[ch - 'a'];

        }

        node.isWord = true;

    }

    /**

     * Returns if the word is in the trie.

     */

    public boolean search(String word) {

        TreeNode node = root;

        for (char ch : word.toCharArray()) {

            if (node.children[ch - 'a'] == null) {

                return false;

            }

            node = node.children[ch - 'a'];

        }

        return node.isWord;

    }

    /**

     * Returns if there is any word in the trie that starts with the given prefix.

     */

    public boolean startsWith(String prefix) {

        TreeNode node = root;

        for (char ch : prefix.toCharArray()) {

            if (node.children[ch - 'a'] == null) {

                return false;

            }

            node = node.children[ch - 'a'];

        }

        return node != null;

    }

}

前缀树的根节点一般不存储字符串,字符串存储在叶子节点中。

前缀和+哈希

如果序列全是正数,可以使用双指针来求解子数组和问题,如果数组不确定正负,那么求解子数组和问题可以使用前缀和和哈希。

给定序列 {x0,x1,x2,x3,x4…xn-1}

依次求解前缀和数组 Si= x0+x1+..+xs

则子数组[i,j]范围内的和可以表示为 Sj-Si-1

class Solution {

    public int subarraySum(int[] nums, int k) {

         Map<Integer, Integer> sumCnt = new HashMap<>();

        int sum = 0;

        int result = 0;

        sumCnt.put(0, 1);

        for (int i=0; i<nums.length; i++) {

            sum += nums[i];

            if (sumCnt.containsKey(sum - k)) {

                result += sumCnt.get(sum - k);

            }

            sumCnt.put(sum, sumCnt.getOrDefault(sum, 0) + 1);

        }

        return result;

    }

}

力扣

排序 计数排序 快速排序 归并排序

计数排序一般用于解决待排序的数据范围是有限的

快速排序

归并排序可以用于链表操作

class Solution {

    public int[][] merge(int[][] intervals) {

         Arrays.sort(intervals, Comparator.comparingInt(o -> o[0]));

        List<int[]> list = new ArrayList<>();

        int i = 0;

        while (i<intervals.length) {

            int j = i+1;

            int[] temp = new int[]{intervals[i][0], intervals[i][1]};

            while (j<intervals.length && intervals[j][0]<=temp[1]) {

                temp[1] = Math.max(temp[1], intervals[j][1]);

                j++;

            }

            list.add(temp);

            i = j;

        }

        int[][] result = new int[list.size()][2];

        for (int k=0; k<list.size(); k++) {

            result[k] = list.get(k);

        }

        return result;

    }

}

力扣

并查集

class Solution {

    public int findCircleNum(int[][] isConnected) {

        int n = isConnected.length;

        int[] num = new int[n];

        for (int i=0; i<n; i++) {

            num[i] = i;

        }

        for (int i=0; i<isConnected.length; i++) {

            for (int j=0; j<isConnected[0].length; j++) {

                // 初始时数量为n 如果i,j属于同一个连通分量 则链接 数量减一

                if (isConnected[i][j] == 1 && union(num, i, j)) {

                    n--;

                }

            }

        }

        return n;

    }

    private boolean union(int[] num, int i, int j) {

        int parentI = findParent(num, i);

        int parentJ = findParent(num, j);

        if (parentI == parentJ) {

            return false;

        }

        num[parentI] = parentJ;

        return true;

    }

    private int findParent(int[] num, int x) {

        while (num[x] != x) {

            x = num[x];

        }

        return x;

    }

}

力扣

拓扑排序

class Solution {

    public int[] findOrder(int numCourses, int[][] prerequisites) {

       List<List<Integer>> graph = new ArrayList<>();

        for (int i = 0; i < numCourses; i++) {

            graph.add(new ArrayList<>());

        }

        int[] indegree = new int[numCourses];

        for (int[] arr : prerequisites) {

            // arr[1]指向arr[0] arr[0]入度加一 arr[1]邻接链表增加关系

            indegree[arr[0]]++;

            List<Integer> edge = graph.get(arr[1]);

            edge.add(arr[0]);

        }

        Stack<Integer> stack = new Stack<>();

        for (int i=0; i<numCourses; i++) {

            if (indegree[i] == 0) {

                stack.add(i);

            }

        }

        List<Integer> result = new ArrayList<>();

        while (!stack.isEmpty()) {

            int num = stack.pop();

            result.add(num);

            List<Integer> edge = graph.get(num);

            for (int e : edge) {

                indegree[e]--;

                if (indegree[e] == 0) {

                    stack.add(e);

                }

            }

        }

        if (result.size() != numCourses) {

            return new int[] {};

        }

        return result.stream().mapToInt(Integer::valueOf).toArray();

    }

}

力扣

回溯法

做一件事情有很多步骤,每一个步骤得有很多选择,做出某一选择后进行系列逻辑判断 看是否符合目标值 如果不符合 则回退该选择 进行下一个选择尝试,如果符合,则将结果加入到结果集中,如果需要进行下一个选择,则可以继续下一个选择尝试。

回溯法本质是一种DFS搜索算法,整个问题解决空间是一棵树,从根节点初始状态,分析根节点下一个节点选择,对于某一个节点,分析当前路径是否满足解,如果满足则加入到结果集中,如果不满足,并且已经到达搜索停止条件,则直接返回,如果不满足解,但是可以通过下一个节点来进行更多的探索,则递归进行探索。

class Solution {

      public List<List<Integer>> combinationSum(int[] candidates, int target) {

        List<List<Integer>> result = new ArrayList<>();

        Arrays.sort(candidates);

        dfs(result, new ArrayList<>(), 0, target, 0, candidates);

        return result;

    }

    private void dfs(List<List<Integer>> result, List<Integer> path, int sum,

            int target, int index, int[] candidates) {

        // 如果和等于目标值 说明当前path中元素符合条件 加入结果集

        if (sum == target) {

            result.add(new ArrayList<>(path));

            return;

        }

        // 如果当前遍历索引大于等于数组长度 则说明后续已没有继续可行解 返回即可。

        if (index >= candidates.length) {

            return;

        }

        // 如果当前和大于target 则无需继续搜索,因为数组元素为正整数,继续累加sum会一直增大

        if (sum > target) {

            return;

        }

        // index数字不加入到集合中

        dfs(result, path, sum, target, index + 1, candidates);

        // index数字加入到集合中

        path.add(candidates[index]);

        sum = sum + candidates[index];

        dfs(result, path, sum, target, index, candidates);

        path.remove(path.size() - 1);

        sum = sum - candidates[index];

    }

}

力扣

class Solution {

   public List<List<Integer>> allPathsSourceTarget(int[][] graph) {

        List<List<Integer>> result = new ArrayList<>();

        List<Integer> path = new ArrayList<>();

        path.add(0);

        dfs(result, path, graph, 0);

        return result;

    }

    private void dfs(List<List<Integer>> result, List<Integer> path, int[][] graph, int index) {

        if (index == graph.length - 1) {

            result.add(new ArrayList<>(path));

            return;

        }

        int[] arr = graph[index];

        for (int num: arr) {

            path.add(num);

            dfs(result, path, graph, num);

            path.remove(path.size() - 1);

        }

    }

}

力扣

动态规划

一般用来求解极值问题,一个规模N的问题,可以由一个规模更小的问题(<N)来解决,一般使用动态规划来求解,关键在于递推关系式以及初始条件。

单序列,一般是 f(n)  f(n-1)  f(n-2)的关系

力扣

双序列,一般是f(n) f(n-1) g(n) g(n-1)的关系

力扣

二维动态规划 f(i,j) 与f(i-1,j) f(I,j-1) f(i-1, j-1)的关系

力扣

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值