专题一:挑战字符串
题目1:无重复字符的最长子串
- 通过时间:40min
- 解题思路:滑动窗口,依靠unordered_map实现
题目描述
给定一个字符串,请你找出其中不含有重复字符的最长子串
的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是`"abc"`,所以其长度为`3`。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是`"b"`,所以其长度为`1`。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是`"wke"`,所以其长度为`3`。请注意,你的答案必须是`子串`的长度,"pwke" 是一个子序列,不是子串。
算法
- brute force:通过两个循环,每次从位置i开始向后遍历,如果出现了一个字符s[j]已经出现过,那么计算j-i的长度是否比已经保存的长度更长,若更长,则替换;并且再次从位置i+1开始向后遍历。
- 滑动窗口:位置i遍历过后到j停下,则[i,j)皆是无重复的子串。故再次从i+1遍历时,[i+1,j)这一段无须重复遍历,从j开始即可。
- 优化滑动窗口:位置i遍历过后到j停下,说明[i,j)有出现和s[j]一样的字符。找到[i,j)中和s[j]一样字符的位置last_j,将i移到last_j+1,j接着从上次的位置往后一步开始
代码
-
brute force :略
-
滑动窗口: 略
-
优化滑动窗口
class Solution { public: int lengthOfLongestSubstring(string s) { // parameters int len = s.length(); int last_j = 0; int longestLen = 0, i = 0, j = 0; // 滑动窗口 unordered_map<char, int> myMap; // key - 字符,value - 在字符串s中的下标 while (i < len && j < len) { for (j = last_j; j < len; j++) { if (myMap.find(s[j]) == myMap.end()) myMap[s[j]] = j; else { // 如果找到的字符是在位置i之前出现过的,只需更新位置,无需理会 int index_repeat = myMap[s[j]]; if (index_repeat < i) { myMap[s[j]] = j; continue; } // 是否需要更新最长子串的长度 int lenOfSub = j - i; longestLen = lenOfSub > longestLen ? lenOfSub : longestLen; // 更新i i = index_repeat + 1; // 更新map中的映射 myMap[s[j]] = j; last_j = j + 1; break; } } if (j == len) { int lenOfSub = j - i; longestLen = lenOfSub > longestLen ? lenOfSub : longestLen; } } return longestLen; } };
题目2:简化路径
- 通过时间:20min
- 解题思路:栈
题目描述
以 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"
算法
粗看题目时并没有什么特别好的想法,看了几个样例后发现了一些端倪。核心思想:将两个斜杠之间出现的‘字符串’压栈,但是获取的子字符串不一定是合法的目录名,需要对获得的子字符串判断:
- 若字符串为
".."
进行出栈操作 - 若字符串为
"."
跳过 - 若字符串为合法的目录名,则压栈
- 当然,过程中多余的
/
要去掉
代码
-
canonical path.h
// // Created by shayue on 2019-04-11. // 利用栈 #ifndef EXPLORE_BYTEDANCE_CANONICAL_PATH_H #define EXPLORE_BYTEDANCE_CANONICAL_PATH_H #include <string> #include <iostream> #include <vector> using namespace std; class Solution { public: string simplifyPath(string path) { vector<string> myStack; string ret; // 1. 规范化处理(这步可能有些多余,输入的path应该本身就是合法的,所以不会出现第一个字符不是'/'的情况) if (path[0] != '/') path = '/' + path; // 2. LOOP int len = path.size(), i = 0, j; while (i < len) { if (path[i] == '/') { i++; continue; } else { string sub; // 获取两个斜杠之间的子字符串 for (j = i; j < len && path[j] != '/'; j++) sub += path[j]; // 更新i i = j; // 判断子字符串的意义 if (sub == ".") continue; else if (sub == "..") { if (!myStack.empty()) myStack.pop_back(); } else myStack.push_back(sub); } } // 3. return if (myStack.empty()) return "/"; else { for (i = 0; i < myStack.size(); i++) { string adds = "/" + myStack[i]; ret += adds; } return ret; } } }; #endif //EXPLORE_BYTEDANCE_CANONICAL_PATH_H
-
main.cpp
#include <iostream> #include "Questions/canonical path.h" using namespace std; int main() { Solution s; string s1 = "/home/"; string s2 = "/../"; string s3 = "/home//foo/"; string s4 = "/a/./b/../../c/"; string s5 = "/a/../../b/../c//.//"; string s6 = "/a//b////c/d//././/.."; cout << s.simplifyPath(s1) << endl; cout << s.simplifyPath(s2) << endl; cout << s.simplifyPath(s3) << endl; cout << s.simplifyPath(s4) << endl; cout << s.simplifyPath(s5) << endl; cout << s.simplifyPath(s6) << endl; return 0; }
-
结果

题目3:复原IP地址
- 通过时间:25min
- 解题思路:枚举所有可能
题目描述
给定一个只包含数字的字符串,复原它并返回所有可能的 IP
地址格式。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
算法
这题一开始我打算用深搜做,但是后来转念一想。取点的位置就那么几个,再怎么复杂都不会超时。下面给出合法IP地址的几个点:
- 总的字符串长度len属于[4,12]
- 在取3个点将字符串分成4部分,分别判断每部分是不是合法。
那么怎么判断各个部分是不是合法的:
- 各个部分的长度小于等于3
- 各个部分转成10进制小于等于255
- 各个部分如果长度不为1,不能出现0在首位的情况。形如“00”,“010”就是不合法的。而“0”是合法的
代码
-
Longest Substring Without Repeating Characters.h
// // Created by shayue on 2019-04-11. // 给定一串数字,找到所有可能的IP地址。 // IP地址最多12个数字,分成4部分,且每部分最多为不大于255的数字 #ifndef EXPLORE_BYTEDANCE_RESTORE_IP_ADDRESSES_H #define EXPLORE_BYTEDANCE_RESTORE_IP_ADDRESSES_H #include <string> #include <vector> using namespace std; class Solution { public: vector<string> restoreIpAddresses(string s) { vector<string> ret; int len = s.length(); // 若传入的数字长度不符合,则不可能形成合法的IP地址 if (len < 4 || len > 12) return ret; // 随机对这些数字取3个点的位置并判断每部分是否合法,进而选择是否加入返回的序列中 for (int i = 1; i < len && i <= 3; i++) { // 255.255.11.135 string part1 = s.substr(0, i); if (!isLegal(part1)) continue; for (int j = 1; i+j < len && j <= 3; j++) { string part2 = s.substr(i, j); if (!isLegal(part2)) continue; for (int z = 1; i+j+z < len && z <= 3; z++) { string part3 = s.substr(i+j, z); if (!isLegal(part3)) continue; string part4 = s.substr(i+j+z); if (!isLegal(part4)) continue; else { string IP; IP.append(part1); IP.append("."); IP.append(part2); IP.append("."); IP.append(part3); IP.append("."); IP.append(part4); ret.push_back(IP); } } } } return ret; } bool isLegal(string subs) { int len = subs.size(); if (len > 3) return false; if (len == 2 || len == 3) { if (subs[0] == '0') return false; } int inter = 0; for (int i = 0; i < len; i++) inter = inter * 10 + (subs[i] -'0'); return inter <= 255; } }; #endif //EXPLORE_BYTEDANCE_RESTORE_IP_ADDRESSES_H
-
main.cpp
#include <iostream> #include "Questions/Restore IP Addresses.h" using namespace std; int main() { string str = "00000"; Solution s; vector<string> rec = s.restoreIpAddresses(str); for (int i = 0; i < rec.size(); i++) cout << rec[i] << endl; return 0; }
专题二:数组与排序
题目1:三数之和
- 通过时间:40min
- 解题思路:a+b=-c,双指针
题目描述
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
算法
代码
//
// Created by shayue on 2019-04-11.
//
#ifndef EXPLORE_BYTEDANCE_3SUM_H
#define EXPLORE_BYTEDANCE_3SUM_H
#include <vector>
#include <algorithm>
using namespace std;
#define MIN_INF 0x80000000
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
int len = int(nums.size()), last_num_i = MIN_INF;
int i, j, k;
vector<vector<int>> ret;
for (i = 0; i < len - 2; i++)
{
if (nums[i] == last_num_i)
continue;
else
last_num_i = nums[i];
/* 在i之后的位置中寻找nums[j]+nums[k]=-nums[i] */
k = len - 1;
j = i + 1;
while (j < k)
{
if (nums[j] + nums[k] == -nums[i])
{
ret.push_back(vector<int>{nums[i], nums[j], nums[k]});
int mid = nums[j];
last_num_i = nums[k];
while (j < k && nums[j] == mid)
j++;
while (j < k && nums[k] == last_num_i)
k--;
}
else if (nums[j] + nums[k] < -nums[i])
j++;
else
k--;
}
while (i+1 < len && nums[i] == nums[i+1])
i++;
}
return ret;
}
};
#endif //EXPLORE_BYTEDANCE_3SUM_H
/*
* TEST
* Solution s;
vector<int> vec = {-1, 0, 1, 2, -1, -4};
vector<vector<int> > rec = s.threeSum(vec);
for (int i = 0; i < rec.size(); i++)
{
for (int j = 0; j < rec[i].size(); j++)
cout << rec[i][j] << ' ';
cout << endl;
}
return 0;
*/
题目2:岛屿的最大面积
- 通过时间:跪了
- 解题思路:DFS
题目描述
给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。)
示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是11,因为岛屿只能包含水平或垂直的四个方向的‘1’。
示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。
注意: 给定的矩阵grid 的长度和宽度都不超过 50。
算法
这题做了我2个小时。一开始傻傻地以为两次dp应该没啥问题,调bug调到后来都怀疑人生了。如果用dp做,应该要4次,从左上到右下,从右下到左上,从右上到左下,从左下到右上。最终某个较大值应该在“岛屿”的“中心”获得。我考虑2两个小时只考虑到前两种,真的是给跪了。考虑到dp还有状态转移方程的判断,这样的话还不如老实用DFS做。事实证明这题用DFS做居然:

用DFS做应该要设置一个访问标记数组,因为连在一起的岛屿铁定一次都访问掉了。假设没有进行标记,然后从之前深搜到过位置再进行DFS,肯定走之前相同的路。设置访问标记数组能大大降低时间复杂度。
这题让我反思了很多,做算法题和数学题一样,一定要找准第一步。想到用什么方法,然后去验证可行性,一定能做到事半功倍。千万不能死扣在一种思维上。我的2个小时。。。
代码
//
// Created by shayue on 2019-04-11.
// DFS
#ifndef EXPLORE_BYTEDANCE_MAX_AREA_OF_ISLAND_H
#define EXPLORE_BYTEDANCE_MAX_AREA_OF_ISLAND_H
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int visited[60][60]; // 0 - 为访问,1 - 访问
int maxAreaOfIsland(vector<vector<int>>& grid) {
int maxArea = 0;
int row = grid.size();
int col = grid[0].size();
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (grid[i][j] == 1 && visited[i][j] == 0)
{
visited[i][j] = 1;
int area = 1;
DFS(i, j, row, col, area, grid);
if (area > maxArea)
maxArea = area;
}
}
}
return maxArea;
}
void DFS(int x, int y, int row, int col, int &area, vector<vector<int>>& grid)
{
if (x+1 < row && grid[x+1][y] == 1 && visited[x+1][y] == 0)
{
visited[x+1][y] = 1;
area += 1;
DFS(x+1, y, row, col, area, grid);
}
if (y+1 < col && grid[x][y+1] == 1 && visited[x][y+1] == 0)
{
visited[x][y+1] = 1;
area += 1;
DFS(x, y+1, row, col, area, grid);
}
if (x-1 >= 0 && grid[x-1][y] == 1 && visited[x-1][y] == 0)
{
visited[x-1][y] = 1;
area += 1;
DFS(x-1, y, row, col, area, grid);
}
if (y-1 >= 0 && grid[x][y-1] == 1 && visited[x][y-1] == 0)
{
visited[x][y-1] = 1;
area += 1;
DFS(x, y-1, row, col, area, grid);
}
}
};
#endif //EXPLORE_BYTEDANCE_MAX_AREA_OF_ISLAND_H
题目3:搜索旋转排序数组
- 通过时间:30min
- 二分查找
题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
算法
虽然数组从某个点被打乱,但是还是局部有序的,这就为使用二分查找提供了前提。还是和一般的二分查找相似,先判断中点和target的关系,然后判断target和两个端点的关系。
代码
//
// Created by shayue on 2019-04-12.
// 二分搜索
#ifndef EXPLORE_BYTEDANCE_SEARCH_IN_ROTATED_SORTED_ARRAY_H
#define EXPLORE_BYTEDANCE_SEARCH_IN_ROTATED_SORTED_ARRAY_H
#include <vector>
#include <iostream>
using namespace std;
class Solution {
public:
int search(vector<int>& nums, int target) {
int len = nums.size();
int left = 0, right = len - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (nums[mid] == target)
return mid;
if (nums[left] <= nums[mid] && nums[mid] <= nums[right])
{
if (target > nums[mid])
left = mid + 1;
else
right = mid - 1;
}
else if (nums[left] <= nums[mid] && nums[mid] >= nums[right])
{
// 左边是有序的,如target大于nums[mid]只有可能在右边
if (target > nums[mid])
left = mid + 1;
else
{
if (target >= nums[left])
right = mid - 1;
else
left = mid + 1;
}
}
else if (nums[left] >= nums[mid] && nums[right] >= nums[mid])
{
// 右边是有序的,如target小于nums[mid]只有可能在左边
if (target < nums[mid])
right = mid - 1;
else
{
if (target <= nums[right])
left = mid + 1;
else
right = mid - 1;
}
}
}
return -1;
}
};
#endif //EXPLORE_BYTEDANCE_SEARCH_IN_ROTATED_SORTED_ARRAY_H
题目4:朋友圈
- 通过时间:25min
- 解题思路:并查集
题目描述
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:
N 在[1,200]的范围内。
对于所有学生,有M[i][i] = 1。
如果有M[i][j] = 1,则有M[j][i] = 1。
算法
使用并查集为每位学生找到一个圈子,这个圈子由一个学生编号确定。比如学生1,2,3属于同一个圈子,那么这个圈子有个带头人-编号1,则对编号1,2,3寻找带头人,最终都能指向编号1.这样就形成了一个圈子。
由于输入的矩阵是对称矩阵,遍历的时候只需要遍历对角线上半部分即可。
代码
//
// Created by shayue on 2019-04-12.
//
#ifndef EXPLORE_BYTEDANCE_FRIEND_CIRCLES_H
#define EXPLORE_BYTEDANCE_FRIEND_CIRCLES_H
#include <vector>
#include <iostream>
using namespace std;
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
// parameters
int numOfStu = M.size(); // 学生的编号为[0, numOfStu-1]
int Set[numOfStu];
// 1. 将并查集初始化,即每个学生的带头人都是其本身
for (int i = 0; i < numOfStu; i++)
Set[i] = i;
// 2. 遍历矩阵M,只需遍历对角线上方即可。当M[i][j]==1时,视情况而定处理编号i和编号j所处的圈子
for (int i = 0; i < numOfStu; i++)
{
for (int j = i+1; j < numOfStu; j++)
{
if (M[i][j] == 1)
{
int headi = findHead(i, Set);
int headj = findHead(j, Set);
if (headi != headj)
Set[headj] = headi; // 当学生i和学生j的圈子未相连时,将他们连在一起
}
}
}
// 确定有几个带头人
int visited[300] = {0};
int ret = 0;
for (int i = 0; i < numOfStu; i++)
{
int head = findHead(i, Set);
if (visited[head] == 0)
{
visited[head]++;
ret++;
}
}
return ret;
}
/* 这个函数用来寻找每个学生对应的带头人 */
int findHead(int pos, const int Set[])
{
// 如果这个条件不满足,说明当前编号pos并不是最终的带头人
while (Set[pos] != pos)
pos = Set[pos];
return pos;
}
};
#endif //EXPLORE_BYTEDANCE_FRIEND_CIRCLES_H