leetcode 字节跳动探索系列

目录
  • 第一章 挑战字符串
    • 无重复字符的最长子串
    • 最长公共前缀
    • 字符串的排列
    • 字符串相乘
    • 翻转字符串里的单词
    • 简化路径
    • 复原IP地址

注: 后来发现这个专题的题目在题库里都有对应的题,故不再更新.将在github里更新对应题库里的题目:
https://github.com/jiangxiewei/acm
leetcode 字节跳动探索系列_第1张图片
专题链接: https://leetcode-cn.com/explore/interview/card/bytedance/

第一章 挑战字符串

无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

双指针方案

本题旨在寻找最长无重复子串,因此目标窗口是连续且长度可变化的,此处第一感觉基本就是双指针.
设置s指针作为起始指针,e为结束指针,str为字符串数组,str[x]为x未知的字符
s指针作为起始位置,e指针作为结束位置. 初始s=e=0 ,接下来只要一边控制e指针往右一边判断区间[s,e]是否存在重复的字符串即可.
判断字符串重复与否的判断可通过建立查询表结局,比如Map. 随着窗口往右移动(str[e]的统计+1),判断加入的字符串是否已存在,若不存在,则继续移动e指针;若已存在,则将s指针向右移动并减去对应字符的统计数(str[s]的统计值-1),至新的[s,e]区间不存在重复字符即可(即当前str[e]的统计值变回1或者说s指针移动到遇到的第一个str[e]字符的下一个)

    public int lengthOfLongestSubstring(String s) {
        if (s.length() == 0) {
            return 0;
        }
        char[] chars = s.toCharArray();
        int maxCount = 1, count = 1, start = 0;
        int[] ccMap = new int[256];
        ccMap[chars[start]]++;
        for (int end = 1; end < chars.length; end++) {
            int currentAdded = ++ccMap[chars[end]];
            count++;
            if (currentAdded > 1) {
                //左指针开始移动
                while (ccMap[chars[end]] > 1 && start < end) {
                    ccMap[chars[start++]]--;
                    count--;
                }
            } else {
                //右指针继续
                maxCount = Math.max(maxCount, count);
            }
        }
        return maxCount;
    }

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。

直接来

直接贴code

    public String longestCommonPrefix(String[] strs) {
        if (strs.length == 0) {
            return "";
        } else if (strs.length == 1) {
            return strs[0];
        }
        int min = Integer.MAX_VALUE;
        for (String s : strs) {
            min = Math.min(s.length(), min);
        }
        int[] mappingCount = new int[256];
        int maxCommon = 0;
        for (int i = 0; i < min; i++) {
            char lastChar = 0;
            for (String s : strs) {
                lastChar = s.charAt(i);
                mappingCount[lastChar]++;
            }
            if (mappingCount[lastChar] % strs.length != 0) {
                return strs[0].substring(0, maxCommon);
            } else {
                maxCommon++;
            }
        }
        return strs[0].substring(0, maxCommon);
    }

字符串的排列

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串
注意:
输入的字符串只包含小写字母
两个字符串的长度都在 [1, 10,000] 之间

双指针

思路与题1别无二致,双指针. 判断是否为A字符串的排列之一,其实只要判断每个字符出现的次数是否一致即可.
在题一(无重复字符的最长子串) 中,我们一边滑动一边实时更新维护着[字符->统计数]的一个查询表,现在我们让A字符串也生成一个这样的查询表,然后我们让双指针动起来直到实时维护的查询表与A字符串的查询表内容一致时,即是我们的目标.

    public boolean checkInclusion(String s1, String s2) {
        SearchStatus ss = new SearchStatus();
        for (char c : s1.toCharArray()) {
            ss.add(c);
        }
        SearchStatus sscp = ss.clone();
        char[] chars = s2.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if (!sscp.find(chars[i])) {
                i -= (ss.total - sscp.total) - 1;
                sscp = ss.clone();
            }
            if (sscp.total == 0) {
                return true;
            }
        }
        return false;
    }
	// 对应了目标字符串的模式,每次重新查找生成一个新的克隆
    public class SearchStatus {
        int[] mapping = new int[256];
        int total = 0;

        public void add(char c) {
            this.total++;
            this.mapping[c]++;
        }

        public boolean find(char c) {
            --this.mapping[c];
            if (--this.total < 0 || this.mapping[c] < 0) {
                return false;
            } else {
                return true;
            }
        }

        @Override
        protected SearchStatus clone() {
            SearchStatus ss = new SearchStatus();
            ss.mapping = Arrays.copyOf(this.mapping, 256);
            ss.total = this.total;
            return ss;
        }
    }

字符串相乘

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

分解

123 * 456= ( 1*100 + 2*10 + 3*1 ) * ( 4*100 + 5*10 + 6*1 ) = 1*100*( 4*100 + 5*10 + 6*1 ) + 2*10*( 4*100 + 5*10 + 6*1 )+ ...
拆到最后,我们可以把任何长度的两个数相乘变成许多个个位数相乘再乘上一个十的倍数即a*b*(10^n).
所以此处我做了一个数组arr[],下标代表第n个十位数,arr[x]*(10^x)便是arr[x]的真实含义.
而最终结果值为 arr[i]*(10^i) + arr[i+1]*(10^(i+1)) + ... + arr[i+n]*(10^(i+n))
接下来就是遍历A字符串的每个数字去与B字符串的每个数字进行两位数乘法运算即可(记得进位),乘完放入arr对应的十倍数位即可.

    public static final char BASE_CH = '0';

    public String multiply(String num1, String num2) {
        int[] base = new int[300];
        char[] ch1 = num1.toCharArray();
        char[] ch2 = num2.toCharArray();

        for (int i = ch1.length - 1; i >= 0; i--) {
            for (int j = ch2.length - 1; j >= 0; j--) {
                int mult = (ch1[i] - BASE_CH) * (ch2[j] - BASE_CH);
                int baseIndex = (ch1.length - 1 - i) + (ch2.length - 1 - j);
                base[baseIndex] += mult % 10;
                base[baseIndex + 1] += mult / 10 + base[baseIndex] / 10;
                base[baseIndex] %= 10;
            }
        }

        StringBuilder sb = new StringBuilder();
        boolean prefixZero = true;
        for (int i = base.length - 1; i >= 0; i--) {
            if (prefixZero) {
                prefixZero = base[i] == 0;
            }
            if (!prefixZero) {
                sb.append(base[i]);
            }
        }
        return sb.length() == 0 ? "0" : sb.toString();
    }

翻转字符串里的单词

给定一个字符串,逐个翻转字符串中的每个单词。
说明:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

字符串API

split时使用正则分割,然后将得到的数组反过来打印即可.

    public String reverseWords(String s) {
        LinkedList linkedList = new LinkedList<>();
        for (String word : s.split("[ ]+")) {
            if (!"".equals(word)) {
                linkedList.addFirst(word);
            }
        }
        StringJoiner result = new StringJoiner(" ");
        for (String word : linkedList) {
            result.add(word);
        }
        return result.toString();
    }

简化路径

以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径
请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。

一个个来

此题其实就是按/分割,然后一个个读取路径来就行.简单的模拟题. 同样可用字符串的正则split分割,然后一个个读路径,遇到"."啥也不干,遇到目录追加进去,遇到".."返回上层.

    public String simplifyPath(String path) {
        LinkedList dirList = new LinkedList<>();
        String[] dirs = s.split("[/]+");
        for (String dir : dirs) {
            switch (dir) {
                case ".":
                    break;
                case "..":
                    if (dirList.size() > 0) {
                        dirList.removeLast();
                    }
                    break;
                case "":
                    break;
                default:
                    dirList.addLast(dir);
            }
        }
        StringJoiner sb = new StringJoiner("/", "/", "");
        dirList.forEach(sb::add);
        return sb.toString();
    }

复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

递归

题目描述简单粗暴,规则也简单粗暴,我的基本思路就是递归+剪枝.
递归每一层的状态是上一个点的位置,然后通过生成下一个点的位置进入下一层,然后不断加入剪枝规则来优化.我目前为了快速通过没有更进一步剪枝.

    private List resultSet;

    public List restoreIpAddresses(String s) {
        resultSet = new LinkedList<>();
        dfs(s.toCharArray(), 0, 4);
        return resultSet;
    }

    private LinkedList current = new LinkedList<>();

    public void dfs(char[] origin, int cp, int remainPointNum) {
        if (cp == origin.length && remainPointNum == 0) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < current.size(); i++) {
                sb.append(current.get(i));
            }
            sb.deleteCharAt(sb.length() - 1);
            resultSet.add(sb.toString());
        } else if (remainPointNum <= 0) {
            return;
        }
        for (int i = 0; i < 3; i++) {
            if (cp + i >= origin.length || (i > 0 && origin[cp] == '0')) {
                return;
            }
            //i决定取几位数字
            int parsed = 0;
            for (int j = 0; j <= i; j++) {
                parsed += (origin[cp + j] - '0') * Math.pow(10, (i - j));
            }
            if (parsed > 255) continue;
            for (int j = 0; j <= i; j++) {
                current.add(origin[cp + j]);
            }
            current.add('.');
            dfs(origin, cp + i + 1, remainPointNum - 1);
            current.removeLast();
            for (int j = 0; j <= i; j++) {
                current.removeLast();
            }
        }
    }

你可能感兴趣的:(leetcode 字节跳动探索系列)