
Leetcode-探索字节跳动官方网址:
探索字节跳动 - 力扣 (LeetCode)leetcode-cn.com
数组排序部分传送门:
爱吃咖喱的皮特:Leetcode-探索字节跳动(官方题库)-思路与解法分析-数组与排序(持续更新)zhuanlan.zhihu.com
Leetcode官方提供了许多著名企业官方整理的题目小合集,这些题目由于是官方整理,所以在面试准备中显得极有针对性:既可以按照对应企业的出题考察点提高自己对应的能力,也可以快速地找到Leetcode的大量题库中相对价值更高的一些题目(比如某道题特别贴合企业的某些业务场景)。
字节跳动共为面试者整理了38道题,分为6个部分:字符串,数组与排序,链表与树,动态或贪心,数据结构,拓展练习。今天我就将其中字符串部分的七道题的思路及代码分享给大家
Question 1: Longest Substring Without Repeating Characters(Leetcode-03)
题目描述
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路
滑动窗口法;初始化滑动窗口,长度为1,窗口内容为s.charAt(0);若窗口右侧字母不存在与窗口内,说明窗口可以拓展,则向右拓展;否则不可以拓展,记录下当前窗口大小,并且将窗口向右缩小,即将窗口左侧index加一
代码
public class Solution {
public int lengthOfLongestSubstring(String s) {
int length = s.length();
int max = 0;
int i=0;
int j=0;
Set<Character> set = new HashSet<>();
while(i<length&&j<length){
if(!set.contains(s.charAt(j))){
set.add(s.charAt(j));
j++;
max = Math.max(max,j-i);
}else{
set.remove(s.charAt(i));
i++;
}
}
return max;
}
}
Question 2: Longest Common Prefix(Leetcode-14)
题目描述
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
思路
解法1:水平扫描法
对于字符串数组{s1,s2,s3,...sn},我们获得它们的最长公共子串(S(i,j)表示字符串si和sj的最长公共子串)可以依据以下方法:S(1,n)=S(S(S(S(1,2),3),4),...n)。即,先获得s1和s2的最长公共子串,再获得这个最长公共子串与s3的最长公共子串,以此类推。此方法的初始最长公共子串为s1(相当于s1与自己的最长公共子串),然后逐渐长度减小,直到符合全字符串数组。
解法2:垂直扫描法(见下代码)
另外一种方法是,我们不像刚刚的方法一样将最长公共子串由长变短,而是由短变长。我们从0开始迭代,对于每次迭代,我们验证index=i的位置上的所有字符串是否都为同一字符,以此类推,从而得到最长公共子串
解法3:分治法
我们可以采用分治法,即S(1,n) = S(S(1,n/2),S(n/2+1,n)) = ...,直到分到每组只有一个字符串,这样就归结到了简单的最长公共子串的计算了
代码
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs.length==0){
return "";
}
StringBuilder stringBuilder = new StringBuilder();
int current = 0;
boolean flag = true;
while (flag){
Character character = null;
for (String s:strs){
if (current<s.length()){
if (character==null){
character = s.charAt(current);
}else if (!character.equals(s.charAt(current))){
flag = false;
break;
}
}else{
flag=false;
}
}
if (flag&&character!=null){
stringBuilder.append(character);
}
current++;
}
return stringBuilder.toString();
}
}
Question 3: Permutation in String(Leetcode-567)
题目描述
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:
输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").
示例2:
输入: s1= "ab" s2 = "eidboaoo"
输出: False
思路
题目问a字符串里面是否含有b的排列。这等价于在a中找一个长度等于b且各字母出现频率相同的子字符串。我们用一个sliding window模拟这个过程。初始化窗口为最左侧的且长度等于b字符串的长度的窗口。在窗口滑动的过程中,我们可以减少部分运算,因为相邻的两个窗口的字母频率数组差别其实只有两个字母,即前一个窗口的第一个字母和后一个窗口的最后一个字母处。
代码
public class Solution {
public boolean checkInclusion(String s1, String s2) {
if (s1.length() > s2.length()){
return false;
}
int length1=s1.length();
int length2=s2.length();
int[] frequency1 = new int[26];
int[] frequency2 = new int[26];
//填充 字符串1 和 字符串2的前一部分(长度等于字符串1)的频率数组
for (int i=0;i<length1;i++){
frequency1[s1.charAt(i)-'a']++;
frequency2[s2.charAt(i)-'a']++;
}
for (int i=0;i<length2-length1;i++){
if (compare(frequency1,frequency2)){
return true;
}
frequency2[s2.charAt(i)-'a']--;
frequency2[s2.charAt(i+length1)-'a']++;
}
return compare(frequency1, frequency2);
}
private boolean compare(int[] frequency1, int[] frequency2) {
for (int i=0;i<26;i++){
if (frequency1[i]!=frequency2[i]){
return false;
}
}
return true;
}
}
Question 4: Multiply Strings(Leetcode-43)
题目描述
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:
输入: num1 = "123", num2 = "456"
输出: "56088"
说明:
num1
和num2
的长度小于110。num1
和num2
只包含数字0-9
。num1
和num2
均不以零开头,除非是数字 0 本身。- 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。
思路
限制条件是我们不能直接转化成整数,所以我们按照最基本的乘法手算算法来进行逐位计算,如下
45
*123
----
15 (3*5)
12 (3*4)
10 ...
8 ...
5 ...
4 (1*4)
----
5535
其中,两位数字相乘的结果(如3*4=12)具体是放在哪个位置是由相乘的两个数处于的数字中的位置所决定的
对于字符串中数字的获得,可以直接使用s.charAt(i)-'0',因为ascii码的特性
另外,结果要求首位没有空格,我们单独对这个要求进行一下处理即可
代码
class Solution {
public String multiply(String num1, String num2) {
int length1=num1.length();
int length2=num2.length();
int[] output = new int[length1+length2];
for (int i=0;i<length1;i++){
for (int j=0;j<length2;j++){
int a = num1.charAt(length1-1-i)-'0';
int b = num2.charAt(length2-1-j)-'0';
// System.out.println("a="+a+",b="+b);
mulAndAdd(output,i+j,a*b);
// System.out.println(Arrays.toString(output));
}
}
StringBuilder stringBuilder = new StringBuilder();
for (int i=0;i<output.length;i++){
stringBuilder.append(output[i]);
}
// System.out.println(stringBuilder.toString());
while (stringBuilder.length()>1){
if (stringBuilder.charAt(0)=='0'){
stringBuilder.deleteCharAt(0);
}else {
break;
}
}
return stringBuilder.toString();
}
private void mulAndAdd(int[] output, int loc, int num) {
output[output.length-1-loc] += num%10;
if (output[output.length-1-loc]>=10){
output[output.length-1-loc]-=10;
output[output.length-1-loc-1]+=1;
}
output[output.length-1-loc-1] += num/10;
if (output[output.length-1-loc-1]>=10){
output[output.length-1-loc-1]-=10;
output[output.length-1-loc-2]+=1;
}
}
}
Question 5: Reverse Words in a String(Leetcode-151)
题目描述
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
- 无空格字符构成一个单词。
- 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
- 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
思路
我们从字符串末端开始向前遍历字符串,在这个过程中,我们需要解决两个问题,即,空格的处理和普通单词的处理。对于字符的处理,由于我们是从后向前遍历字符串,而结果要求我们对单个的单词保持原状,所以我们采用reverse操作。对于空格的处理,题目要求去掉荣誉空格,我们简单地用一个布尔遍历记录是否有空格即可。
最后,题目要求字符串前后端的空格要被消除,我们只用简单盯着字符串头尾是否是空格即可,如果是空格,就删除掉。
代码
class Solution {
public String reverseWords(String s) {
int length = s.length();
int index = length-1;
StringBuilder stringBuilder = new StringBuilder();
while (index>=0){
boolean addSpace = false;
while (index>=0&&s.charAt(index)==' '){
addSpace = true;
index--;
}
if (addSpace){
stringBuilder.append(" ");
}
StringBuilder local = new StringBuilder();
while (index>=0&&s.charAt(index)!=' '){
local.append(s.charAt(index));
index--;
}
if (local.length()>0){
stringBuilder.append(local.reverse().toString());
}
}
while (stringBuilder.length()>0){
if (stringBuilder.charAt(0)==' '){
stringBuilder.deleteCharAt(0);
}else {
break;
}
}
while (stringBuilder.length()>0){
if (stringBuilder.charAt(stringBuilder.length()-1)==' '){
stringBuilder.deleteCharAt(stringBuilder.length()-1);
}else {
break;
}
}
return stringBuilder.toString();
}
}
Question 6: Simplify Path(Leetcode-71)
题目描述
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径
请注意,返回的规范路径必须始终以斜杠 /
开头,并且两个目录名之间必须只有一个斜杠 /
。最后一个目录名(如果存在)不能以 /
结尾。此外,规范路径必须是表示绝对路径的最短字符串。
示例 1:
输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:
输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:"/a/./b/../../c/"
输出:"/c"
示例 5:
输入:"/a/../../b/../c//.//"
输出:"/c"
示例 6:
输入:"/a//b////c/d//././/.."
输出:"/a/b/c"
思路
我们先总结一下简化路径的要求: 1.字符串末尾没有斜杠 2.多个连续斜杠用一个斜杠代替 3.由根目录不能向上一级,所以碰到这种情况时,返回结果认为依然是根目录 4.一个点.表示当前目录 5.两个连续的点..表示父目录
分析要求后,我们决定采用栈的数据结构,将每一层级路径的解析抽象为入栈和出栈操作。由于要求中的第四条与第二条,我们在处理路径时可能遇到需要跳过(即不入栈)的操作,所以我们使用hashset存储需要跳过的字符,这样在判断是否需要跳过时,我们只需要O(1)即可完成
代码
class Solution {
public String simplifyPath(String path) {
Deque<String> stack = new LinkedList<>();
Set<String> skip = new HashSet<>(Arrays.asList("..",".",""));
for (String dir : path.split("/")) {
if (dir.equals("..") && !stack.isEmpty()){
stack.pop();
}
else if (!skip.contains(dir)) {
stack.push(dir);
}
}
String res = "";
for (String dir : stack) {
res = "/" + dir + res;
}
return res.isEmpty() ? "/" : res;
}
}
Question 7: Restore IP Address(Leetcode-93)
题目描述
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
思路
题目要求我们返回所有的有效的ip地址,我们可以将这个问题分为两个子问题分别进行解决。问题1:如何将输入字符串分割为所有可能有效的四部分;问题2:如何判断被分割好的字符串是否有效
问题2很好解决,只需要验证每部分的长度以及大小符合ip地址的定义即可
问题1我们可以用回溯法来枚举所有的可能分割;回溯法的另一个好处是可以减少部分运算。例如我们分割到了第二部分,发现第二部分的分割结果只有两种有效的方法(如输入:255332421,我们的第一部分分割到255,第二部分只能分割为3或者33),这样可以避免计算第二部分分割为332的所有计算,因为一个有效的ip地址必定是四个部分全部有效的
代码
class Solution {
List<String> res = new ArrayList<String>();
ArrayList<String> single = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
if (s.length()>12){
return res;
}
allPossible(4,s);
return res;
}
private void allPossible(int times, String s) {
if (times==1){
if (s.length()<=3&&s.length()>0&&Integer.parseInt(s)<=255&&Integer.parseInt(s)>=0){
if (!(s.length()>1&&s.charAt(0)=='0')){
single.add(s);
res.add(stringArrayToString(single));
single.remove(s);
}
}
}else {
int endLength = Math.min(s.length(),3);
if (s.length()!=0&&s.charAt(0)=='0'){
endLength=1;
}
for (int i=1;i<=endLength;i++){
String thisPart = s.substring(0,i);
if (Integer.parseInt(thisPart)<=255&&Integer.parseInt(thisPart)>=0){
single.add(thisPart+".");
System.out.println("single:"+single.toString());
allPossible(times-1,s.substring(i,s.length()));
single.remove(single.size()-1);
}
}
}
}
private String stringArrayToString(ArrayList<String> single) {
StringBuilder stringBuilder = new StringBuilder();
for (String s:single){
stringBuilder.append(s);
}
return stringBuilder.toString();
}
}