目录
search-insert-position(二分法,简单)
generate-parentheses(回溯,DFS,中等)
set-matrix-zeroes(回溯,中等)
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。
示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
思路
第一个大循环,先去找每个零,找到了把对应的上下左右,不是0的位给置成"-1",然后,再来一个大循环,把上一个循环置成"-1"的位,给全部置成“0”,就完成操作了。这是因为,如果第一次就置成“-1”,后面找“0”的时候,会没办法区分开到底是原先的0啊,还是新置成的0了。
PS:这个思路是典型的回溯思想,面试的时候被问到过,这个思路可以直接在输入矩阵上修改。故可以节省空间。
完整code
class Solution {
public:
void setZeroes(vector<vector<int> > &matrix) {
int m = matrix.size();
int n = matrix[0].size();
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (matrix[i][j] == 0)
{
for (int x = 0; x < n; x++)
if(matrix[i][x] != 0)
matrix[i][x] = -1;
for (int y = 0; y < m; y++)
if (matrix[y][j] != 0)
matrix[y][j] = -1;
}
}
}
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (matrix[i][j] == -1)
matrix[i][j] = 0;
}
}
}
};
combination-sum(回溯,DFS,中等)
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
思路
在主函数combinationSum中,首先,将输入进行排序,这个相当于预处理,保证了后面都是按升序来进行。之后,就进入了Sum中,其中,res是存最后的结果数组,temp是存每次进行的处理结果的数组,candidates是输入待处理的数组,后面的第四个参数,是记录每次待处理的元素的下标,target是输入的目标值。
进入Sum函数,先定义出口,和边界条件判断
if (target == 0)//定义递归出口
{
res.push_back(temp);
return;
}
if (target < 0||k >= candidates.size())//边界条件
return;
接着,将上一次的temp的值存入到t里面,然后将这次待比较的candidates中的元素,存入到temp中来,
vector<int>t=temp;
temp.push_back(candidates[k]);
那么精彩之处就来了!把temp给带到递归中去,分两个递归, 如果用当前处理的candidates[k]值,我们可以用来累加得到target,就将这个值压入temp中,并且第四个参数还设置成k,意思是说,我就让candidates[k]一直带入,看看能不能最后得到target(比如,target是6,而candidates里面包含了一个2,那么肯定存在一种可能就是,2+2+2=6,这一步就是为了包含这种情况,有某个元素,在之前累加的基础上,只自己重复累加就可以得到target了。)
如果不用当前处理的candidates[k]值,那么就把t给带进去,因为t是保存的上一次的temp不包含这次的candidates[k],并且,将k+1,开始检查下一个元素。target不变。
整个code最巧妙的方法就在于这儿,通过模拟用或者不用,将所有可能都囊括了其中,每一步都递归出很多可能,最后,被边界条件限制给抹杀掉不合适的,用递归出口存下来符合要求的,很是巧妙。让它们自己繁殖然后,在杀掉不合适的,把合适的保存下来,感觉像造物主一样伟大!牛逼。
Sum(res, temp, candidates, k , target- candidates[k]);//用
Sum(res, t, candidates, k + 1, target);//不用
完整code
class Solution {
public:
vector<vector<int> > combinationSum(vector<int> &candidates, int target) {
vector<vector<int> > res;
vector<int> temp;
sort(candidates.begin(), candidates.end());
Sum(res, temp, candidates, 0, target);
return res;
}
void Sum(vector<vector<int>> &res, vector<int> &temp, vector<int> &candidates, int k, int target) {
if (target == 0)
{
res.push_back(temp);
return;
}
if (target < 0||k >= candidates.size())
return;
vector<int>t=temp;
temp.push_back(candidates[k]);
Sum(res, temp, candidates, k , target- candidates[k]);//用
Sum(res, t, candidates, k + 1, target);//不用
}
};
valid-sudoku(回溯,中等)
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
1.数字 1-9 在每一行只能出现一次。 2.数字 1-9 在每一列只能出现一次。 3.数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
上图是一个部分填充的有效的数独。数独部分空格内已填入了数字,空白格用 '.' 表示。
示例 1:
输入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
示例 2:
输入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
思路
本来这个思路用字典来做,特别适合,将每个位置的值记为字典的键,然后出现的次数记为值,如果值大于1就是无效的了。
但下面这个思路也非常巧妙。
首先,设置三个数组,分布代表记录横着的,数着的和3*3小格子的统计情况。将每个格子都填成了0,这就是整个Code最巧妙的地方了!
int row[9][9] = {0};
int col[9][9] = {0};
int grids[3][3][9] = {0};
接着往下看,是一个大循环,一个循环就把结果给算出来了,也是很牛逼啊!
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
}
}
循环遍历每个数组值,如果当前数组值不为空,就进行判断,真正精彩的地方来了,先是定义了index,为什么要减1呢?因为索引都是0开始的,而数组里的数字是1~9,减一是为了让范围变成0~8,对应上下标。
然后,在if语句里面一个++语句,这个表达的就非常抽象了,经过苦思冥想,终于明白了作者的意思。刚开始创建数组的时候,每个位置都是0,而0对应的bool值是false,其他数字对应的bool都是true,那到这里,开始遍历,还要明白一点,如果再某一行有重复的字符出现,那么这两个对应的index也肯定是一样的,以row为例:
首先if判断row[i][index]是ture还是false,如果是0,就不进入循环了,而且,每一步都会执行row[i][index]++,即,row[i][index] = row[i][index] + ‘1’,这样,就把原本是0的值,给加进去一个值了,变成1了,那么这个bool类型也变成true了,那么下一次如果有相同的index出现,再次跳到这个row[i][index]来判断的时候,if是肯定会发现这个地方是已经有值存在了,就会进入if里面,执行returne false了。
int index = board[i][j] - '1';
if(row[i][index]++)
return false;
if(col[j][index]++)
return false;
if(grids[i/3][j/3][index]++)
return false;
完整code
class Solution {
public:
bool isValidSudoku(vector<vector<char> > &board) {
int row[9][9] = {0};
int col[9][9] = {0};
int grids[3][3][9] = {0};
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
if(board[i][j] != '.') {
int index = board[i][j] - '1';
if(row[i][index]++)
return false;
if(col[j][index]++)
return false;
if(grids[i/3][j/3][index]++)
return false;
}
}
}
return true;
}
};
search-insert-position(二分法,简单)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5 输出: 2
示例 2:
输入: [1,3,5,6], 2 输出: 1
示例 3:
输入: [1,3,5,6], 7 输出: 4
示例 4:
输入: [1,3,5,6], 0 输出: 0
思路
二分法是拿mid去更新起末位置,而不是反过来。
如果要找插入的位置是,从前往后数索引,找到待插位置,不是从后往前。
完整code
class Solution {
public:
int searchInsert(int A[], int n, int target) {
int l = 0;
int r = n - 1;
while (l <= r)
{
int mid = (r + l) / 2;
if (A[mid] == target) return mid;
if (A[mid] > target) r = mid - 1;
if (A[mid] < target) l = mid + 1;
}
return l;
}
};
search-for-a-range(遍历,简单)
给出一个有序数组,请在数组中找出目标值的起始位置和结束位置
你的算法的时间复杂度应该在O(log n)之内
如果数组中不存在目标,返回[-1, -1].
例如:
给出的数组是[5, 7, 7, 8, 8, 10],目标值是8, 返回[3, 4].
思路
分两个阶段,第一阶段,先在数组中遍历一遍,如果有数组中与目标值相等的元素,那就把位置索引给保存到index里面。
第二个阶段,如果index为空,说明数组中没有找到与目标值相等的元素,那么,就返回[-1,-1];
如果不为空,那么就把索引的最小的和最大的,作为值,返回出去。
此题,默认输入数组已经是升序排列好了,所以,如此简单。
完整code
class Solution {
public:
vector<int> searchRange(int A[], int n, int target) {
vector<int> res;
vector<int> index;
for (int i = 0; i < n; i++)
{
if (A[i] == target)
index.push_back(i);
}
if (index.size() != 0)
{
res.push_back(index[0]);
int ns = (int)index.size();
res.push_back(index[ns-1]);
return res;
}
else
{
res.push_back(-1);
res.push_back(-1);
return res;
}
}
};
generate-parentheses(回溯,DFS,中等)
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
思路
主要是辅助函数func,输入第一个参数tmp,用来存放每次生成的新括号序列,后面两个参数,记录左、右两半边括号的个数。
设置递归出口,如果左右括号数同时全部归零,说明都用完了,可以返回,因为返回类型是void,所以,,没有return也是可以的,直接将生成的tmp,存到了结果res里面。并且清空tmp,便于下一次存放新的括号序列。
if (l == 0 && r == 0) {
res.push_back(tmp);
tmp.clear();
}
接着,如果左括号数没用完,或者说,右边没用完的括号数量比左边的大,都需要继续进行递归。
如果左边数没用完,下一次递归的时候,就直接在tmp里面添一个“(”,同时l-1。当(r>l)的时候,也是如此。
这个地方需要注意的是,会不会出现:“))((”的情况出现?不会的,这也是这个Code的精彩的地方,整个思路其实是利用了递归回调的时候,来进行的。因为判断(l>0)这个条件在前,所以,一开始,肯定有落单的左括号,然后,只要有左括号落单,就会有右括号去填补(r>l)而且,也保证了右括号至多和左括号相等不会超过左括号的数量,所以,无形中,其实是按照n个连续左括号,n-1个连续左括号,n-2个连续左括号。。。。的思路,来进行的。
if (l > 0) {
func(tmp + '(', l - 1, r);
}
if (r > l) {
func(tmp + ')', l, r - 1);
}
完整code
class Solution {
public:
vector<string> res;
vector<string> generateParenthesis(int n) {
if (n <= 0)
return res;
string tmp;
func(tmp, n, n);
return res;
}
void func(string tmp, int l, int r) {
if (l == 0 && r == 0) {
res.push_back(tmp);
tmp.clear();
}
else {
if (l > 0) {
func(tmp + '(', l - 1, r);
}
if (r > l) {
func(tmp + ')', l, r - 1);
}
}
}
};