0
点赞
收藏
分享

微信扫一扫

算法题总结的几种题型

程序员伟杰 2022-03-24 阅读 38

刷算法的心得

前言

刷了270道算法,记录自己的心得体会,主要包括具体题型总结和抽象题型总结。

一、具体题型总结

具体的题型总结就是线性表的数组、链表、队列的操作和应用,树的四种遍历、回溯、剪枝,图的DFS、BFS等,动态规划,前缀树等。

二、抽象题型总结

我把每个知识点封装成一个component,如component-DFS、component-动态规划等。把数据结构应用看出plugin来辅助component完成算法题解。两类可自由组合。总结了五种题型。

1、预处理Pre + component

1)单词演变:预处理成图+BFS-component
在这里插入图片描述

	//单向BFS+代码优化,减少运行时间。
    public int ladderLength2(String beginWord, String endWord, List<String> wordList) {
        //1-预处理:将worldList中的单词作为一个节点,将其之间的关系映射为图关系,然后物理结构预处理成数组式的邻接表。
        //2-BFS操作:通过广度优先搜索来求源点到终点的最短路径。
        for (String s : wordList) addEdge(s);

        if (!wordId.containsKey(endWord)) return 0;
        addEdge(beginWord);

        int begin = wordId.get(beginWord), end = wordId.get(endWord);
        int[] dis = new int[nodeNum];//用该数组既能记住起点到所有节点的最短路径,还能避免加入以访问过的节点避免死循环。
        dis[begin] = 1;

        Queue<Integer> queue = new LinkedList<>();
        queue.offer(begin);

        while (!queue.isEmpty()) {
            int cur = queue.poll();

            for (Integer id : edge.get(cur)) {
                if (dis[id] == 0) {
                    dis[id] = dis[cur] + 1;
                    queue.offer(id);
                }
                if (id == end) return (dis[id] >>> 1) + 1;

            }
        }
        return 0;
    }

    Map<String, Integer> wordId = new HashMap<>();
    List<List<Integer>> edge = new ArrayList<>();
    int nodeNum = 0;

    private void addEdge(String s) {
        addNode(s);

        int len = s.length();
        char[] chs = s.toCharArray();

        for (int i = 0; i < len; i++) {
            char originC = chs[i];
            chs[i] = '*';

            String newWord = String.valueOf(chs);
            addNode(newWord);
            int idx1 = wordId.get(s), idx2 = wordId.get(newWord);
            edge.get(idx1).add(idx2);
            edge.get(idx2).add(idx1);

            chs[i] = originC;
        }
    }

    private void addNode(String s) {
        if (!wordId.containsKey(s)) {
            wordId.put(s, nodeNum++);
            edge.add(new ArrayList<>());
        }
    }

    //双向BFS,通过减少没必要的distance计算,从而减少计算时间。2^8^ > 2^4^ + 2^4^(毕竟单向广度优先,越往后走,要以指数级扩展。)
    public int ladderLength3(String beginWord, String endWord, List<String> wordList) {
        for (String s : wordList) addEdge(s);

        if (!wordId.containsKey(endWord)) return 0;
        addEdge(beginWord);

        int begin = wordId.get(beginWord), end = wordId.get(endWord);
        int[] dis1 = new int[nodeNum];//用该数组既能记住起点到所有节点的最短路径,还能避免加入以访问过的节点避免死循环。
        int[] dis2 = new int[nodeNum];
        dis1[begin] = 1;
        dis2[end] = 1;

        Queue<Integer> queue1 = new LinkedList<>();
        Queue<Integer> queue2 = new LinkedList<>();
        queue1.offer(begin);
        queue2.offer(end);

        while (!queue1.isEmpty() && !queue2.isEmpty()) {
            int curB = queue1.poll(), curE = queue2.poll();

            for (Integer id : edge.get(curB)) {
                if (dis1[id] == 0) {
                    dis1[id] = dis1[curB] + 1;
                    queue1.offer(id);
                }
                if (dis2[id] != 0) {
                    return dis1[id] + dis2[id] >>> 1;
                }
            }
            for (Integer id : edge.get(curE)) {
                if (dis2[id] == 0) {
                    dis2[id] = dis2[curE] + 1;
                    queue2.offer(id);
                }
                if (dis1[id] != 0 && dis2[id] != 0) {
                    return dis1[id] + dis2[id] >>> 1;
                }
            }
        }
        return 0;
    }

2)开锁密码:预处理成图+component-BFS
在这里插入图片描述

package com.xhu.offer.offerII;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

//开锁密码
public class OpenLock {
    //双向BFS-component
    public int openLock(String[] deadends, String target) {
        Set<String> dead = new HashSet<>();

        for (String deadend : deadends) dead.add(deadend);

        //为了避免BFS多一层就是一层指数级增长,所以先把begin==end判断了。
        if (dead.contains("0000")) return -1;
        if (target.equals("0000")) return 0;

        Queue<String> q1 = new LinkedList<>();
        Queue<String> q2 = new LinkedList<>();
        q1.offer("0000");
        q2.offer(target);
        int[] dis1 = new int[10000];
        int[] dis2 = new int[10000];
        dis1[0] = 1;
        dis2[Integer.parseInt(target)] = 1;

        while (!q1.isEmpty() && !q2.isEmpty()) {
            int r = computingDis(q1, dis1, dis2, dead);
            if (r != -1) return r;

            r = computingDis(q2, dis2, dis1, dead);
            if (r != -1) return r;
        }
        return -1;
    }

    private int computingDis(Queue<String> q, int[] dis1, int[] dis2, Set<String> dead) {
        String cur = q.poll();
        int idx = Integer.parseInt(cur);
        char[] chs = cur.toCharArray();

        for (int i = 0; i < 4; i++) {
            char originC = chs[i];
            int t = (originC + 1 - '0') % 10 + '0';
            chs[i] = (char) t;
            String newS = String.valueOf(chs);
            int j = Integer.parseInt(newS);
            if (!dead.contains(newS) && dis1[j] == 0) {
                q.offer(newS);
                dis1[j] = dis1[idx] + 1;
            }
            if (dis2[j] != 0) {
                return dis1[j] + dis2[j] - 2;
            }

            t = (originC + 9 - '0') % 10 + '0';
            chs[i] = (char) t;
            newS = String.valueOf(chs);
            j = Integer.parseInt(newS);
            if (!dead.contains(newS) && dis1[j] == 0) {
                q.offer(newS);
                dis1[j] = dis1[idx] + 1;
            }
            if (dis2[j] != 0) {
                return dis1[j] + dis2[j] - 2;
            }

            chs[i] = originC;
        }
        return -1;
    }
}

2、component + component

复原IP:component-DP + component-DFS
在这里插入图片描述

//和拆解成多少回文字符串一样,那里是满足是否为回文,这里是满足是否为不含前导为0且转化为的数字在0-255之间。
    //dp+DFS
    public List<String> restoreIpAddresses(String s) {
        int len = s.length();
        int[][] dp = new int[len][len];
        //dp
        for (int i = 0; i < len; i++) {
            for (int j = i; j >= 0; j--) {
                //超过三位数肯定大于255,而且后面的都大于3位数了,直接break。
                if (i - j > 2) break;
                //前导为0的肯定不行,只能看看后面是否有希望
                if (s.charAt(j) == '0' && i != j) continue;
                //筛选0-255的数字
                int m = Integer.parseInt(s.substring(j, i + 1));
                if (m <= 255) dp[i][j] = 1;
            }
        }
        //dfs
        dfs(s, dp, 0, 0);

        return res;
    }

    List<String> res = new ArrayList<>();
    StringBuilder sb = new StringBuilder();

    //总结:急解决不了问题,反而会导致思路乱起来停滞不前,没有将压力转化为动力,而是压力自然变成了巨大无比的阻力。该调试调试,该理思路理思路...
    private void dfs(String s, int[][] dp, int cur, int level) {
        if (level == 4) {
            if (cur == s.length()) res.add(sb.delete(sb.length() - 1, sb.length()).toString());//去掉最后的点
            return;
        }
        for (int i = 0; i + cur < s.length(); i++) {
            if (dp[cur + i][cur] == 0) continue;
            sb.append(Integer.parseInt(s.substring(cur, cur + 1 + i))).append('.');
            dfs(s, dp, cur + 1 + i, level + 1);
            sb.delete(cur + level, sb.length());//bug1:直接从cur删是有问题的,没有考虑加的点所占的位置。
        }
    }

3、转化问题 + component +[plugin]

百度笔试题:找到数组内的两个最长子数组,要求这两个子数组的0和1的数字出现次数相等。

1)转化问题
可以看见两个数组可以拥有同样的中间部分,中间部分的0和1两者肯定是相等的,那么只需找到左边和右边相等的数字,让左右的之间的距离尽可能的大。
2)如何找到相等还要尽可能的大?可以component-双for循环 + plugin-剪枝。

package baidu;

import java.util.Scanner;

public class Main2 {
    //总结:其实细心读题很关键,分析题转换问题很关键,往这方面走,而不是往套模板走。当然现在还没有到达套模板的境界,先刷800题。
    //编程只是工具,一般编程不难,重要核心是剖析问题的本质,想办法解决,然后再用程序实现该办法。
    //每天学习要进步,而不是老走老路,要不断反思,不断改变自己的思考和学习等方法,才能往前走,可以多去实践经历,会找到本质的成长,心也踏实一些。
    private static void myFunc(String s) {
        int len = s.length();
        int mid = len >>> 1;

        //找两端两个相等的两个数,让两端相减最大。
        int max = 0, start = 0, end = 0;
        for (int i = 0; i < mid; i++) {
            //j - i + 1 > max剪枝
            for (int j = len - 1; j - i + 1 > max && j >= mid; j--) {
                if (s.charAt(i) == s.charAt(j)) {
                    int gap = j - i + 1;
                    if (gap > max) {
                        max = gap;
                        start = i + 1;
                        end = j + 1;
                    }
                }
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append(start).append(' ').append(end - 1).append(' ').append(start + 1).append(' ').append(end);
        System.out.println(start + " " + (end - 1) + " " + (start + 1) + " " + end);

    }

    public static void main(String[] args) {
        Scanner scin = new Scanner(System.in);

        String s = scin.nextLine().trim();

        myFunc(s);
    }


}

4、component + DS-plugin/普通plugin

最长斐波拉契数列:component-DP + HashMap-plugin
在这里插入图片描述

	//hashmap处理+动态规划(往后遍历,每加一个元素,都要看前面那两个元素符合,所以需要固定一个元素,所以双循环,另一元素确定用map快速查找)
    public int lenLongestFibSubseq(int[] arr) {
        int len = arr.length, ans = 0;
        Map<Integer, Integer> cache = new HashMap<>();//map解决前面可能符合条件太多的状态,以O(1)快速找到,而不是一个一个去试。
        int[][] dp = new int[len][len];//i到j的最长斐波拉契数列
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                dp[i][j] = 2;
                int gap = arr[j] - arr[i];
                if (cache.containsKey(gap)) {
                    int l = cache.get(gap);
                    dp[i][j] = dp[l][i] + 1;
                    ans = Math.max(ans, dp[i][j]);
                }
                cache.put(arr[i], i);
            }
        }
        return ans < 3 ? 0: ans;
    }

5、理清逻辑就行(easy题型)

百度笔试题:把数组放大K倍

package baidu;

import java.util.Scanner;

public class Main1 {
    //总结:理清思路,想好思路是否是一种可行的方案,这需要很多实践和积累才能快速的判断。
    private static int[][] myFunc(int n, int k, int[][] nums) {
        int newN = n * k;
        int[][] res = new int[newN][newN];
        int i = 0;
        for (int[] num : nums) {
            //扩展第i行
            int j = 0;
            for (int val : num) {
                for (int u = j * k; u < j * k + k; u++) {
                    res[i * k][u] = val;
                }
                j++;
            }
            //复制开始一行到下面k - 1 行。
            for (int u = i * k + 1; u < i * k + k; u++) {
                for (int o = 0; o < newN; o++) {
                    res[u][o] = res[u - 1][o];
                }
            }
            i++;
        }
        return res;
    }

    public static void main(String[] args) {
        Scanner scin = new Scanner(System.in);

        String[] strs = scin.nextLine().trim().split(" ");
        int n = Integer.parseInt(strs[0]), k = Integer.parseInt(strs[1]);

        int[][] nums = new int[n][n];
        for (int i = 0; i < n; i++) {
            strs = scin.nextLine().trim().split(" ");
            for (int j = 0; j < n; j++) {
                nums[i][j] = Integer.parseInt(strs[j]);
            }
        }
        int[][] res = myFunc(n, k, nums);
        for (int[] re : res) {
            for (int i : re) {
                System.out.print(i);
                System.out.print(' ');
            }
            System.out.println();
        }
    }
}

总结

1)基本知识点是基本功,可去牛客网分类刷题,打牢基本功,即熟练掌握基本知识点,如数组、链表、栈、队列、二叉树、图、排序、查找、动态规划、前缀树的相关知识点。
2)在基本功掌握扎实了,封装知识点为component、plugin、问题刨析并转换,难题都是这三种的组合。这三种可以搭起任何难题。不过要自己多积累,多刷题,多总结。
3)除此之外,多见识学习总结别人的代码,上面的组合能解决问题,但是还有个关键点是尽可能的减少运行时间和空间消耗,多刷题见识各种coding技巧。如BFS中的dis数组一变量多用,既可以放在被访问的节点不被访问,还可以不记录当前队列的层数;双向BFS;虚节点;等等。

参考文献

[1] LeetCode 刷题+算法竞赛+找工作
[2] 牛客网 刷题+学习+交流+找工作神器

举报

相关推荐

0 条评论