数据结构与算法题目及C++解答

前言

题目主要按照类型进行整理,包括leetcode,nowcoder等网站,对于可以使用多种方法的题目,不重复列举。推荐书籍《数据结构与算法分析--C++语言描述》第四版。

本文中所有源代码及博客中其他文章的VS源代码均在github:https://github.com/AnkangH,根据名称检索即可。

1.排序算法 SortAlgorithm

排序算法的源码见博客内排序算法的文章。

1.1.1 颜色分类

给定一个由0,1,2 三个数字组成的无序数组,每个数字的数目未知。求其排序后的数组。

因为数字有界,因此使用桶排序,使用三个桶,桶索引对应数字,桶中的计数为对应数字的出现次数。有序从桶中取出数字,即可得到有序数组。时间复杂度O(N),空间复杂度O(N)。

Leetcode  (medium)  75. 颜色分类

c++解答

1.1.2  寻找两个排序数组的中位数

题目:给定两个排序数组,求其中位数。要求时间复杂度O(log(m+n))。

解析:记数组1大小m,数组2大小n,那么中位数为合并后的排序数组的最中间两个数字nums[mid1] nums[mid2]的平均值,或者最中间的那个数字nums[mid1]。使用两个指针i,j遍历每个数组,判断其数字nums1[i]和nums2[j]大小,小的作为合并后数组的当前值,用一个变量index记录合并后数组的索引。若一个数组遍历完,则遍历剩余的数组,否则比较当前值,当index=mid1和mid2时,记录两个值。暂时实现时间复杂度O(m+n)的一趟扫描算法。O(log(m+n))留待日后解决。

Leetcode  (hard)  4. 寻找两个有序数组的中位数

c++解答

1.1.3  字符串归一化

给定一个小写字母的字符串序列,按照字典顺序输出每个出现的字母及其出现次数。

桶排序,使用26个桶储存每个小写字母出现的次数。取出桶的顺序按照升序,即可做到字典顺序,桶不为空则说明该字母出现过,输出其次数即可。

牛客网  2019校招真题  字符串归一化

c++解答

1.1.4 字符串排序

给定一个字符串,后六位为数字,输出后六位排序后的数值。

截取后6位,使用stl的sort即可。

牛客网  2019校招真题  字符串排序

c++解答

1.1.5  归并两个有序数组

给定两个有序数组,顺序输出每个数字

双指针法,遍历每个数组,判断指针所指向的数字大小。

牛客网  2019校招真题  合并数组

c++解答

1.1.6  合并多个排序链表

给定多个排序链表,每个链表中的节点值升序。将多个链表合并为一个链表。

使用堆排序,将所有链表头放入小顶堆中。当前节点值较小节点在堆顶,每次取堆顶构建链表,并删除堆顶,将堆顶节点的下个节点入堆。要注意堆模板没有链表结构的比较函数,需要自定义比较函数。因为没有指定空节点的比较函数,所以要在入堆时去掉所有空节点。

struct cmp//排序函数名
    {
        bool operator() (ListNode* a, ListNode* b) //节点类型 ListNode* 
        {
            return a->val>b->val;//堆的大顶小顶与比较函数相反 小顶堆使用>
        }
    };

priority_queue s;//大顶堆 以val比较大小

priority_queue,cmp>;//小顶堆

Leetcode  (hard)  23. 合并K个排序链表

c++解答

1.1.7  数字前后组合的最大数

给定一个数组,将数字组合成最大的数。

对于任意两个数字a和b,比较a在b前和a在b后两个数字的大小,按较大数的组合方法放置a和b的顺序。

Leetcode  (medium)  179. 最大数

c++解答

1.1.8  两两配对差值最小

给定一个数组,两两配对求和,使和的最大值和最小值差值最小。

即要数组中两两求和,并且每个和的差异最小。将数组排序,双指针分别从首尾(最小值和最大值)出发求和,这样将最大值最小值结合,数组中两两求和的差异最小。

牛客网  拼多多2019校招真题  两两配对差值最小

c++解答

1.1.9  缺失的第一个正数

给定一个无序数组,在时间复杂度O(n),空间复杂度O(1)的要求下找到第一个未出现的正数。

数据结构与算法题目及C++解答_第1张图片

时间复杂度O(n)要求遍历,空间复杂度O(1)要求不使用额外储存空间。因此需要in-place排序,数组的正确排序为1~n,i从0开始,若i处的数字nums[i]为i+1,或nums[i]应该在的位置nums[i]-1处的数字已经是正确的nums[i]-1+1=nums[i],或者nums[i]不在正确范围(<=0或>n)都将当前索引i+1,跳向下一个继续交换。在未满足上述三个条件之前,i保持当前值,一直交换。否则将当前位置的数字放到正确的位置上,注意要将正确位置的数字先保存,防止覆盖,直至i>=n退出。这样数组中所有i~n的数字都在数组的正确位置,那么遍历排序后的数组,第一个与索引不对应(nums[i]!=i+1)的数字即为未出现的第一个正数,否则n+1为未出现的第一个正数。

Leetcode  (medium)    41. 缺失的第一个正数    c++解答

1.1.9  牛牛找工作

给定一组工作,每个工作有难度和报酬两个属性,给定一组人,每个人有能力值属性,当这个人的能力值大于工作的难度时,即可选择这个工作并获得报酬,求每个人的最大报酬。

数据结构与算法题目及C++解答_第2张图片

题目的核心问题是每个工作的难度和报酬无关,即工作难度低的报酬可能更多。对于每个人都要在工作中搜素,找到能力值大于工作难度的所有工作中,报酬最大那个。如果顺序搜素所有工作,那么时间复杂度o(n*n)会超时。因此考虑,能力低的人所能获取的最大报酬一定不大于工作能力高的人,因为能力高的人其工作选择更多。因此工作按照先报酬降序,报酬相同的能力降序,同时每个人按照能力降序。这样第一个人的能力最大,在工作中搜素到的第一个工作即为其能获取的最大报酬,将这个索引作为第二个人搜素的起点。同时需要按照每个人的输入顺序输出报酬,因此需要先保存输入顺序,并使用哈希表记录每个能力所能获取的最大报酬。

牛客网  2019校招真题      牛牛找工作      c++解答

2.图论

2.1深度优先搜索DFS

搜索时先对可能的情况继续执行下一步搜索,直到不满足或者到达终点,所以叫做深度优先(类比二叉树记忆,根节点出发,直到叶节点再继续下一个搜索)。通常适用于可达性问题,如从一个起点出发,是否能够到达某个条件的终点。

递归形式的dfs是for循环的变体(可以这么认为),对于多层嵌套,且层数不定的情况,使用递归来进行函数语句复用,完成函数的编写。因为是递归,所以要设定终止条件,即可以在递归中判断,不满足时退出,也可以先判断条件,满足时再进行递归。

回溯法,即dfs+剪枝,如果要找一条路径,而在中间某个节点发现走不通,那么从这个节点开始之后的所有节点的可能组合都不可行,从而将这个节点作为根节点的所有枝都剪掉,而返回到这个节点的下一个顺序搜索节点。注意递归的传值和传址,当使用传址时,所有dfs的子函数都使用同一个变量,因此退出时,需要删除本轮添加的节点;当使用传值时,每个dfs的子函数均使用其副本,即一个独立的变量,不需要删除本轮添加的节点,但是时间复杂度不佳,因为传值使用副本,需要新建一个变量。

时间复杂度和空间复杂度优化:dfs中的所有复用变量,如数组大小,矩阵行数,列数等,使用全局变量。dfs所有参数,尽量使用传址,即使用引用。

2.1.1 矩阵中的最大连通区域

二维矩阵4连通DFS

Leetcode 695.Max Area of Island https://leetcode.com/problems/max-area-of-island/description/

C++解答:https://github.com/AnkangH/LeetCode/blob/master/DFS/695.%20Max%20Area%20of%20Island.cpp

2.1.2 矩阵中元素的连通关系

该题目有两种解答,不相交集类或者dfs搜索均可。

Leetcode 547. Friend Circles https://leetcode.com/problems/friend-circles/description/

C++解答:https://github.com/AnkangH/LeetCode/blob/master/DFS/547.%20Friend%20Circles.cpp

2.1.3 矩阵中连通区域的数目

给定一个二维矩阵,0代表海洋,1代表陆地,求所有岛屿的数目。将矩阵外设为海洋。即矩阵边界上的陆地也算作岛屿。

dfs上下左右四个方向,将所有能搜索到的1修改为0,防止下次搜索时重复。遍历矩阵,当前为1即进行dfs,增加计数。

Leetcode  (medium)  200. 岛屿数量

c++解答

2.1.4 二叉树的路径

DFS回溯法,返回上一层时,删除该层添加的内容。如dfs的参数不使用引用,那么不需回溯, 因为每次都是将当前节点加到路径之后。

Leetcode 257. Binary Tree Paths https://leetcode.com/problems/binary-tree-paths/description/

C++解答:https://github.com/AnkangH/LeetCode/blob/master/DFS/257.%20Binary%20Tree%20Paths.cpp

2.1.5 矩阵中的路径

如果路径上某个条件不符,说明不是该路径,搜索时该路径上所有修改的变量都应该复原。牛客网与Leetcode的题目相同,只是实现方式不同,牛客网使用一维数目保存矩阵,使用char[]保存路径,leetcode使用vector>保存矩阵,使用string保存路径。建议都做一下,熟练运用不同的数据类型。复用变量使用全局变量,不要在dfs函数中新建变量,dfs中的参数,除了基本类型(整形)等,只要是结构类变量,如vector,string尽量使用传址,即引用。优化后Leetcode 时间36ms(<85%),空间9.8m(<99%)。

Leetcode 79. Word Search https://leetcode.com/problems/word-search/

牛客网《剑指OFFER》65.矩阵中的路径:

https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

C++解答:https://github.com/AnkangH/LeetCode/blob/master/DFS/079.%20Word%20Search.cpp

2.1.6 矩阵中被包围元素的修改

注意不要使用回溯法,当发现不可修改时,返回将修改的变量复原。而应该将'O'分类,先处理从边界出发dfs到的所有'O',保存为'O'"X"之外的其他字符,再将内部'O'修改为'X',再将前述修改为其他字符的字符复原为'O'即可。理由:

考虑如图所示的矩阵,搜索时若顺序是O2->O1,O2->O3,那么03发现边界上的'O'之后,无法将O2处已经修改为'X'的'O'复原,因为该递归已退出,同理不能使用vector<>保存修改过的变量,因为判断是否复原的条件是dfs(up)||dfs(down)||dfs(left)||dfs(right),为真则复原,为假则清空,那么如果在之前发现了需要修改的O,此时队列中多出很多错误的点,无法处理,也不要使用返回值,返回值只能在相邻递归(调用和被调用之间传递),而并列的递归(顺序执行的各个条件)无法使用返回值联系起来。

Leetcode  (medium)  130. 被围绕的区域

c++解答

2.1.7 字符串间字符排列问题

使用字符串的索引进行递归,以当前字符串每个不同字符进行dfs。使用当前字符串的副本进入下个递归程序,则不需要回溯删除本轮添加的字符,因为传递的是本轮字符串的副本,所以每个递归程序中的变量是互异的。

Leetcode 17. Letter Combinations of a Phone Number:
https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/

C++解答:https://github.com/AnkangH/LeetCode/blob/master/DFS/017.%20Letter%20Combinations%20of%20a%20Phone%20Number.cpp

2.1.8 有重复元素的排列问题

给定n个字符组成的字符串,字符有重复,输出所有可能的排列。对字符串输入的每个字符,标记当前已读,然后dfs剩余的字符,直到长度为要求的长度。对于重复的字符,不进行dfs搜索,因为对于重复元素,无论使用那个做为第一位进行dfs,产生的序列均是相同的。剪枝不理想,时间复杂度还是较大。

Leetcode  (medium)  47. 全排列 II

c++解答

2.1.9 互异元素排列问题

给定n个互异元素,求所有可能的排列结果。

Leetcode  (medium)  46. 全排列

c++解答

2.1.10 互异元素可重复选择的组合问题

给定n个互异正数,一个正数目标值,从数组中任意(可重复选取某个数字)选取,使和为该目标值。求所有的组合方法。根据目标值与当前值的差更新dfs目标,对数组其他数字dfs。如果当前数字对数组所有元素进行dfs,会出现组合相同但是不同排列的情况,需要将重复的组合剔除。 如果使用哈希表,还需要进行数组排序和转换字符串的过程,方法一可以完成要求,但运行耗时900+ms,是很差的方法。方法二是,当前数字对数组中当前数字之后所有数字进行dfs。这样可以避免重复组合。如arr=2,3,7;target=7。如果使用方法一,会出现2 2 3;2 3 2; 3 2 2;7共四种解决方法,然而前三种是同一种组合。

Leetcode  (medium)  39. 组合总和

c++解答

2.1.11 有重复元素不可重复选取的组合问题

给定n个正整数,无序,可能有重复元素,任意选取(同一索引在一种组合中不可使用多次),使和为目标值,求所有组合方法。

上个题目中,我们知道,即便是互异元素,只有dfs时不是对当前元素的之后进行检索,那么组合也会重复。因而首先确定,dfs是对当前元素之后的元素遍历。再次,相同数字做为首字母出现时,遍历也可能重复,所以排序原数组,仅对重复数字第一次出现时作为首个数字进行dfs。即便做了这些限定,结果仍不尽人意。考虑 arr=2 2 1 2 5,target=5。排序后arr=1 2 2 2 5,使用上述思路的输出为1 2 2,1 2 2,1 2 2,5。数字2作为首数字确实没有输出重复组合,但是当首数字为1时,输出了arr[0] arr[1] arr[2],arr[0] arr[1] arr[3]和arr[0] arr[2] arr[3]。只要重复数字出现的索引是连续的,说明是第一次搜索,无论选取了多少个都可以。如果当前数字arr[cur]的索引cur和dfs时的上个数字arr[pre]的索引pre不连续,即cur-pre>=2,如arr[1]和arr[3],arr[0]和arr[2],说明不是首次选取,此时就要进行检查,如果arr[cur-1]==arr[cur],那么选取pre的dfs遍历结果一定是重复的。

Leetcode  (medium)  40. 组合总和 II

c++解答

2.1.12 互异元素集合的子集合问题

给定一个互异元素的集合,输出所有的子集。注意空集是所有集合的子集。对于每个当前元素,dfs之后的元素即可。因为子集长度不一,所以每个当前集合都保存到最后的结果。也不需要边界条件,实际上隐含的边界条件是,当index超出数组范围后,for循环不执行,因此dfs停止。

Leetcode  (medium)  78. 子集

c++解答

2.1.13 有重复元素集合的子集问题

注意三个点:1.输入中第一个出现的数字再做为首元素进行dfs,即输入数组排序,当前元素与上个元素不相同再作为首元素进行dfs 2.剔除重复序列的方法是 遍历的的当前索引cur如果与下个索引next不连续 即next-cur>=2,那么判断元素arr[next]和其在数组中的上个元素arr[next-1],若相同,则本次遍历之后的所有子集均为重复子集,需要剔除。3.子集长度不定,所以所有当前遍历的子集都保存,且注意空集是任何集合的子集。

Leetcode  (medium)  90. 子集 II

c++解答

2.1.14 数字连续和问题

给定一个正整数sum,求所有连续正整数序列,使得序列和为sum。序列至少包含两个元素。因为至少包含两个数字,所以最大的组合为从sum/2+sum/2+1,即dfs起始数字的范围为1-sum/2。

牛客网 剑指OFFER  和为S的连续正数序列

c++解答

2.1.15 N皇后问题

题目:给定一个nxn的棋盘格,要求放置n个皇后,求所以满足条件的放置方法。放置条件为,每一个皇后的行标不同,列标不同,且任意两个皇后不在同一对角线上,即上下左右和45度角方向,不能有相邻的皇后。

解答:行标递增,对列标进行判断来dfs。使用一个数组arr保存已放置的皇后,size=arr.size(),那么(i,arr[i])i=[0,size-1]即为已放置皇后的坐标,新放置的皇后,因为行标递增,行标为size必不会与已放置的皇后冲突,那么只判断列标[0,n-1]的合法性即可。使用一个bool数组记录列表的放置情况,从而保证不同列,再对所有已放置的皇后(i,arr[i])i=[0,size-1]和当前要放置的皇后 (size,j) j=[0,n],要求abs(i-size)!=abs(arr[i]-j)即可保证不在同一斜线上。

Leetcode  (hard)  51. N皇后

c++解答

2.1.16 N皇后问题 II

同n皇后问题,但是只求放置的方法数目,不求具体路径。将上题中保存方法的数组删除,改用一个变量res记录,当满足条件的列标数目当于n时,res++。

Leetcode  (hard)  52. N皇后 II

c++解答

2.1.17  第k个排列(dfs问题但不适合用dfs求解)

给定一个正整数n,求数组1,2,...,n的全排列中,顺序第k个排列。注意不能使用dfs求全排列,否则一定超时。因为数组互异,所以全排列是有序的,可以根据k的大小划分范围,来确定每一位数字,即k和每个全排列有对应关系。如1234全排列的第6个元素,可以看到:

1234  1
1243  2
1324  3
1342  4
1423  5
1432  6
即,(k-1)/(n-1)!的值可以确定第一位数字的值,即str[0]=num[(k-1)/(n-1)!]。每个数字都有n位,所以使用一个for(i=1;i<=n;i++)循环来计算每一位数字,k从k-1开始。当前index=k/(n-i)!,str+=num[k/(n-1)!],更新k=k-index*(n-1)!。

Leetcode  (medium)  60. 第k个排列

c++解答

2.1.18 N个互异元素的K个组合问题

给定n个互异元素1-n,以及一个数字k ,取出其中k个作为组合,求所有互异组合。回溯问题,dfs+剪枝,index作为数字的索引,当前索引放入结果暂存,当结果中的个数=k时,放入最终结果。对所有i=[index+1,n]的i进行dfs,尾回溯,删除当前轮添加的元素。

 Leetcode  (medium)  77. 组合

c++解答

2.1.19  括号生成

给定一个正整数n,使用n个左括号和n个右括号来生成所有满足括号匹配规则的组合。

根据当前字符串中左括号和右括号的数目,dfs规则如下: 1.当前字符串数目==n*2,保存结果 2.当前字符串中左括号数目==n,即左括号已用完,下次只能放置右括号 3.当前字符串中左括号数目>右括号数目,下次可以放置左括号也可以放置右括号 4.当前字符串中左括号数目==右括号数目,下次只可以放置左括号。 为什么当前字符串中左括号不会小于右括号? 因为这种情况不满足括号匹配规则,起始时先放入一个左括号,再对以上四种情况dfs。 

Leetcode  (medium)  22. 括号生成

c++解答

2.1.20  格雷编码

给定一个n代表二进制位数,求一个可能的格雷编码。格雷编码要求任意相邻的数字,二进制只有一位不同。

1.公式法 g(i)=i^(i/2) 2.回溯法。 由于待选数字为2exp(n)个,规模极大,因为构建枝时,为极大降低规模,对当前数字index,求其二进制每一位不同对应的数字,这样每一层dfs中只有最多n个枝。使用数组记录着2exp(n)个数字有没有使用,只对未使用的数字进行dfs。公式法4ms,回溯法时间复杂度一定大于公式法,12ms,虽然使用回溯法解决这个问题不是最佳,但是作为dfs的练习题来说是很好的。

Leetcode  (medium)  89. 格雷编码

c++解答

2.1.21  二叉树根节点到叶节点的路径和

给定一棵二叉树,求从根节点到叶节点的所有路径中,和为目标值的路径。

根据case判断二叉树的节点值有正有负,所以不能提前剪枝(当前值>sum),只要当前节点有左右子节点,就对其dfs,更新和尾sum-cur->val。如果当前节点值等于sum值,且当前节点为叶节点,则把该路径放入最终结果。注意尾回溯时,不要提前退出,要保证本轮添加的节点值都能在函数末尾回删。

Leetcode  (medium)  113. 路径总和 II

c++解答

2.1.22  二叉树路径之和

给定一棵二叉树,节点值为0-9,从根节点出发到达叶节点的路径上每个节点为十进制数字的一位,叶节点为个位。求二叉树所有路径的数字之和。

回溯法,可以使用副本不进行回删,也可以使用引用进行回删。每次添加当前数值时,先将已有的数字升位,再把当前数字放到个位,若到达叶节点,将当前和放入总的和中即可。

Leetcode  (medium)  129. 求根到叶子节点数字之和

c++解答

2.1.23  组合总数III

给定1-9的数字,一个目标值n和数字个数k,要求从1-9中选取不重复的k个数字,和为n。且组合不能重复。

回溯法,对1-9作为第一个数字i开始进行dfs,dfs的范围是[i+1,9],更新当前和sum-=i,剪枝方法是对列举的所有可能,当i>sum时,不进行dfs。尾回溯,函数尾删除本轮添加的元素。使下次开始时,组合暂存变量temp的值复原。

Leetcode  (medium)  216. 组合总和 III

c++解答

2.1.24  最少货物装箱问题

给定一个箱子大小n,有三种货物体积分别为3,5,7,每种货物的数目无限,若箱子能被装满,输出装满箱子使用的最少货物数目,否则输出-1。注意装箱问题不一定可以使用dfs。考虑1,5,6三种货物和体积为10的箱子,dfs优先找到61111的方案,但是最优方案为5 5。

回溯法,优先级7>5>3,并且需要提前退出,否则搜索的规模太庞大。使用全局bool变量flag,在dfs的枝中加入&&flag,因为搜索的优先级是7>5>3,所以如果箱子能被装满,那么第一个组合一定是使用箱子数目最少的,之后flag=false,这样右侧的所有还未搜索的枝就被全部剪掉了。并且剪的是根节点,而不是每一个到叶结点的路径,所以时间复杂度会大大提高。

牛客网 2019校招真题  最少数量货物的装箱问题

c++解答

2.1.25  分割回文串

给定一个字符串s,将其分割为k个子串,其中每个子串均为回文串,输出所有可能的分割方案。

1.使用dp方法判断每个子串是否为回文串2.回溯法根据dp分割回文串

Leetcode  (medium)  131. 分割回文串

c++解答

2.1.26  根据词典拆分单词

给定一个字符串s和词典dict,如果字符串可以由词典中的单词拆分,输出所有的拆分结果。

不要直接dfs,通过res是否为空来判断是否可以拆分,而应该先使用dp方法判断能否拆分,若能拆分,再根据子串是否在词典中进行回溯拆分,否则一些不能拆分的测试用例dfs的规模太大会直接超时。

Leetcode  (hard)  140. 单词拆分 II

c++解答

2.1.27  扁平化列表迭代器

给定一个列表,列表中的元素可以为数字或者另一个列表,实现迭代器,返回顺序访问的每一个数字。

使用一个数组,保存顺序访问的每一个数字,如果是数字,放入数组,否则对这个列表继续访问直至数字。使用一个指针记录当前位置,使用一个变量记录总的数字个数。

Leetcode  (medium)  341. 扁平化嵌套列表迭代器

c++解答

2.1.28  9宫格数字键盘的字母组合

给定一个手机9宫格键盘,根据输入数字输出所有可能的字母组合。

列举当前数字代表的所有字母放入组合暂存,对下个数字代表的所有字母进行dfs。注意回溯要有两层,即删除当前添加的,删除上一层循环添加的。

Leetcode  (medium)  17. 电话号码的字母组合

c++解答

2.1.29  矩阵中的最长递增路径

给定一个mxn矩阵,求矩阵中的最长递增路径的长度,可以上下左右移动。

使用带备忘录的dfs,仅对满足递增和索引合法的上下左右点进行dfs。如果从某点i,j开始的最长递增路径已经求取,那么直接返回备忘录中的数值,否则对上下左右四个点进行判断,符合要求就进行dfs返回最大路径。

Leetcode  (hard)    329. 矩阵中的最长递增路径    c++解答

2.1.30  分割等和子集

给定一个无序数组,判断能否将数组分为和相等的两个子集。

本题是一个典型的01背包问题,因此可以使用动态规划来解决。动态规划的时间复杂度是O(n*target),因此时间复杂度太高。而使用dfs的时间复杂度较低。注意特殊case 100,1,1,1,1,1这种,因为1太多往往导致dfs超时。但是这种情况,数组最大元素大于sum/2,此时数组一定不能分为两个和相等的子集。

Leetcode  (medium)    416. 分割等和子集    c++解答

2.2 广度优先搜索BFS

搜索时,根据节点离起点的远近划分层次,优先搜索离得近的节点,当所有离得近的节点搜索完时,再搜索下一层。可以对比二叉树的层序遍历来记忆。通常适用于最优性问题,如图论问题中,无权无向图的最小路径等。搜索问题均可通过dfs和bfs来解决,并且dfs和bfs能解决的问题同样多,但是对于可达性问题,如果使用bfs,那么会增加很多不必要的工作,同样对于最优性问题,使用dfs会增加很多不必要的工作。

最小生成树:有权无向连通图中,路径的总权重最小的生成树,使用Prim算法。

最小高度树:无权无向连通图中,某个顶点作为根节点出发,高度最小的树,使用BFS。

2.2.1 二叉树的最小深度

层序遍历,第一个发现的叶节点的深度即为最小深度。

Leetcode 111. Minimum Depth of Binary Tree

c++解答

2.2.2 单词接龙的最短变换次数

单词间是无权无向图,求指定起点到终点的最小路径。

使用哈希表构造邻接表,使用辅助队列来BFS,入队列的单词都标记已读,防止重复访问。

Leetcode  (medium)  127. 单词接龙

c++解答

2.2.3 单词接龙最短变换路径

同2.2.2但是要求给出最短变换的每个路径。

分3步。1.预处理与构建词典的哈希表,便于查表。2.修改词典单词及起始单词的每一位构建邻接表 3.层序遍历 查找最短路径。

步骤3的注意点:1.当前层的单词c和b,如果c的子节点有b,那么跳过,因为a->c->b的路径一定大于a->b的路径。2.当前处理的单词置为已读,同时使用一个哈希表记录当前层的每个单词,一层一层的处理。只有当当前单词的邻接单词不在当前层且不在上面的层中,才可以放入队列。3.使用哈希表记录每个以当前单词结尾的路径,之前单词的路径可以不删除,因为删除之前的路径需要额外的时间。

Leetcode  (hard)  126. 单词接龙 II

c++解答

2.2.4  最小高度树

给定一个无权无向连通图,以任意顶点出发,求最小高度树的高度。

常规方法是以每个顶点出发,BFS构造最小高度树,记录最小高度树的深度。但是时间复杂度太高,超时。采用第二种方法,从叶节点出发,删除叶节点cur和它的枝cur->next,那么倒数第二层的节点nxt会变成叶节点,继续这个逻辑,直到最后的一个或两个节点即为最小高度树的根节点。层序从下向上遍历,因为删除了上层节点向下的枝,所以不需要记录每个节点是否已经访问过。

Leetcode  (medium)  310. 最小高度树

c++解答

2.2.5  课程表

给定一组课程的优先关系,如[a,b]意味着学习课程a之前需要先学习课程b。求是否存在学完所有课程的学习路线。

有向图的拓扑排序,若a->b那么a在b之前。选当前入度为0的顶点a入队列,删除a的所有边,若删除边后,b的入度为0且未读,那么b入队列。最后检查所有顶点是否均已读即可。因为顶点唯一访问,所以只需要将入度数目-1,而不需要在数组中删除该边,也不需要使用数组标记该顶点已访问。

Leetcode  (medium)  207. 课程表

c++解答

2.2.6  课程表II

同课程表,如果有学习路线,那么输出这个学习路线,否则输出空。

bfs拓扑排序,记录入队列当前顶点,其变化即为学习路线。

Leetcode  (medium)  210. 课程表 II

c++解答

2.2.7 火星词典

现有一个字典序,字典序未知但是给出几个单词按照字典序的排序,求可能的字典序。

BFS拓扑排序。每一对相邻单词的第一个不同字母可构建一个邻接关系,如acd acb,有邻接关系d->b,即c在b前,除此之外不能推断出其他字母的先后顺序。注意邻接关系可能重复,如acd acb,bcd bcb,此时d->b出现了两次,求入度时不要重复统计。使用bool型变量记录邻接关系,或使用map或者set去重均可。

Leetcode  (hard)  269. 火星词典    c++解答

3.动态规划和贪心算法

动态规划:问题具有递推性质,如果相求dp[k],那么需要求dp[k-1],...,又dp[0]已知。这类问题的重点在于找到递推关系,而找到递推关系的前提是正确定义了递推变量,对于动态规划问题,只有某个或者某几个性质满足题目要求解的递推性。

贪心算法:局部最优->全局最优。 确定一个参数,当前所进行的操作满足该参数当前最优,且不对之后的最优参数产生影响,注意贪心算法不一定能获得全局最优解。

动态规划通常用来解决子序列和子串问题,如最大和,最长正负等。子串:又称连续子序列,是连续序列的一个连续子集;子序列:相对关系与序列中相同的子集,子集中的元素可以不在序列中是相邻的。

3.1 动态规划

3.1.1 回文子串

dp[i][j]代表从str[i]到str[j]的字符串是否为回文字符,对于dp[i][j],如果i+1<=j-1,那么当s[i]=s[j]且dp[i+1][j-1]为true时,dp[i][j]为true。如果i+1>j-1,那么i和j相邻,那么s[i]=s[j]时,dp[i][j]为true。注意dp[i][j]的递推过程,如果要求dp[0][4]那么必须先求dp[1][3],所以i从size-1开始递减,j>i,递增。

Leetcode  (medium)  5. 最长回文子串

c++解答

牛客网 2019校招真题  回文子串的数目

c++解答

3.1.2 最长回文子序列

给定一个字符串,求其最长回文子序列。

dp[i][j]为s[i,j]中回文序列的最大长度,则dp[i][j]=dp[i+1][j-1]+2,s[i]==s[j],=max(dp[i+1][j],dp[i][j-1]),s[i]!=s[j],j-i>=2;

dp[i][i]=1;dp[i][j]=2,j-i=1&&s[j]==s[i],dp[i][j]=1,j-i=1&&s[j]!=s[i]。由此可见,i递减,j递增,且j>=i。求递推过程,记录最大的dp[i][j]即可。

因为只考虑两端是否相同,和中间的最大回文子序列长度,所以没有拼接作用,因此求的是最大回文字序列的长度。而不是最大回文子串。

Leetcode  (medium)  516. 最长回文子序列

c++解答

牛客网 2019校招真题  回文字符串

c++解答

3.1.3 有障碍物的二维矩阵路径

题目:给定一个二维矩阵,0为空地,1为障碍物。求从矩阵左上角出发,每次可以向右或向下走,求到达右下角的所有路径数目。

解答:有些题目不适合用dfs,比如这道,使用dfs绝对超时。如果只是求路径数目而不求具体的路径,那么使用动态规划最为简单。令dp[i][j]为到达grid[i][j]的路径数目,初始值dp[0][0]=1(gird[0][0]=0),如果gprd[0][0]=1,那么到右下角的路径数目一定为0。dp[i][j]=dp[i-1][j]+dp[i][j-1],因为当前格子只有左侧和上方的格子可以到达,注意i=0和j=0的时候,防止数组越界即可。

Leetcode  (medium)  63. 不同路径 II

c++解答

3.1.4  单词变换的最少次数

给定一个单词word1和word2,可以对word1执行三种操作,添加一个字母,删除一个字母和修改一个字母,求由word1到word2的最少变换次数。

不要使用无权无向图的BFS,因为单词长度很长时,m树的枝会非常多。考虑使用动态规划的方法,记dp[i][j]为由word1前i个字母转换到word2前j个字母所需的最少变换次数,那么由dp[i][j]=min(a,b,c),其中a=dp[i-1][j]+1,即word1的前i-1个字符转换到word2前j个字母所需最少次数k次+1,因为此时只要删除word1的第i个字母即可;b=dp[i][j-1],即word1的前i个字母变换为word2前j个字母所需最少次数+1,因为此时只要在word1第i个字母后插入word2第j个字母即可;c=dp[i-1][j-1](word1[i-1]=word2[j-1],若word1前i-1个字母变换word2前j-1个字母最少需要k次,又word1第i个字母也等于word2第j个字母,那么不需要增加次数),dp[i-1][j-1]+1(若word1前i-1个字母变换到word2前j-1个字母最少需要k次,又word1第i个字母不等于word2第j个字母,那么需要修改word1第i个字母为word2第j个字母,次数+1)。

Leetcode  (hard)  72. 编辑距离

c++解答

3.1.5  三角形最小路径和

给定一个三角形的矩阵,求从顶点出发,到达最后一行顶点的最小路径。每个顶点可以到达下一行的相邻两个顶点。

dfs的规模太庞大,超时。考虑动态规划算法。记dp[i][j]为从顶点出发到达(i,j)点的最短路径和,那么dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j](0

Leetcode  (medium)  120. 三角形最小路径和

c++解答

3.1.6  有正负的数列最大子串乘积

给定一个数列,数值有正有负,求一个子串,使得乘积最大。

因为有正有负,所以不能仅用一个变量记录当前最大乘积cur,若nums[i]*cur

Leetcode  (medium)  152. 乘积最大子序列

c++解答

3.1.7  打家劫舍

给定一个正整数数列,从中任意选取,但是要求不能选择相邻的数字,使数字之和最大。

记dp[i]为偷盗至第i个房间的最大价值,从第3个(索引2)开始,当前的最大价值为dp[i]=max(dp[i-1],dp[i-2]+nums[i]),如果偷盗至相邻项的的价值比偷当前房间大,那么不偷当前房价。否则偷当前房间。因为dp[i]的定义是偷盗至第i个房间,而不是确定偷第i个房间,所以返回dp[size-1]即可。因为更新dp数组时只使用了三个变量,所以可以用三个变量来代替dp数组。优化空间复杂度。

Leetcode  (easy)  198. 打家劫舍

c++解答

3.1.8  矩阵中的最大正方形

给定一个由0和1组成的矩形,求1能组成的最大面积。

记当前元素所能组成的最大矩形边长为dp[i][j],dp[i][j]=0,matrix[i][j]=0;dp[i][j]=max(dp[i-1][j-1])+1。如果当前为0,那么dp为0,否则搜索ij所在行列,遇到‘0’退出,搜索的最大距离为dp[i-1][j-1],搜索到的最大长度+1即为当前元素能围成矩形的最大边长。

Leetcode  (medium)  221. 最大正方形

c++解答

3.1.9  最大子串和

给定一个整数序列,求最大连续子序列,使子序列的和最大。

若dp[i-1]+nums[i]>=0,dp[i]=dp[i-1]+nums[i],否则dp[i]=nums[i];因为dp只与相邻元素有关,所以使用一个变量记录dp[i-1]并更新即可。时间复杂度O(n),空间复杂度O(1)。

Leetcode  (medium)  53. 最大子序和

c++解答

牛客网  2019校招真题  连续子数组的最大和 

c++解答

3.1.10  魔法深渊

要跳上n阶台阶,每次只能跳2的幂阶,求跳上n阶台阶总方法数。

假设dp[k]为跳上k阶台阶的方法数,那么最后一次可以跳1,2,...,2^i(2^i<=n)阶台阶,因此dp[k]=dp[k-1]+dp[k-2]+...+dp[k-2^i]。记录输入台阶的最大值作为递推的终点,记录递推过程的中间值,并按照输入的台阶数输出方法总数即可。

牛客网 2019校招真题  魔法深渊

c++解答

3.1.11  最少数量货物装箱问题

给定一个体积为n的箱子,和三种体积为3,5,7,数目不限的货物,输出装满n的最少货物数目,若不能装满,输出-1。

记dp[i]为装满体积为i的箱子所用的最少货物数目,则dp[i]=min(dp[i-3],dp[i-5],dp[i-7])+1,即最后次有三种可能,装一个体积为3,5,或7的箱子,那么最后一次装了一个箱子,总的次数即在上一个体积的次数上+1。

牛客网  2019校招真题  最少数量货物装箱问题

c++解答

3.1.12  最小代价爬楼梯

给定一个数组,代表跳出每层台阶需要的代价,可以从第0或者第1阶出发,花费代价后可以跳1层或者2层,求跳出第n阶台阶所需的最小代价。

跳出第n层即跳上第n+1层,记dp[i]为跳上第i层所需的最小代价,则dp[0]=0,dp[1]=0,因为可以从第0层或者第1层出发。dp[i]=min(dp[i-1]+costs[i-1],dp[i-2]+costs[i-2]),因为跳上第i层可由第i-1或者第i-2层跳上。

牛客网  2019校招真题  最小代价爬楼梯

c++解答

3.1.13  正则表达式匹配

给定一个由小写字母组成的字符串s,一个由字母和正则运算符号' . ',' * '组成的字符串p,判断p是否能与s匹配。其中'.'可以代表任意一个小写字母,'*'可以匹配0个或n个前面的字符。

dp[i][j]代表s的前i([0,m-1])个字符与p的前j([0,n-1])个字符是否能匹配,dp[0][0]=true,因为两个空字符串匹配。dp[0][1]肯定不能匹配,因为要消去字符,必须有'*',而*之前必须有字母才能发挥作用。如果p的子串最后一个字符为*,那么判断删除*及删除*和*之前的字母后,当前子串p能否与子串s匹配,即dp[0][j]=dp[0][j-1]||dp[0][j-2],p[j-1]==*,j>=2。对于其余的dp[i][j],因为dp[i][0]肯定为fasle,所以不需另外赋初值。dp[i][j],若s[i-1]==p[j-1],即s子串最后一个字母等于p子串最后一个字母或者p子串最后一个字母为'.',那么判断删除这个字母后的两个子串是否匹配,即dp[i][j]=dp[i-1][j-1],s[i-1]==p[j-1];否则判断p子串最后一个字母是否为'*',若为'*',且j>=2,那么*可以发挥作用,判断p子串倒数第二个字母是否等于'.',或者是否等于s子串最后一个字母,如果等于,那么通过对*的操作,可能使子串s和p匹配,有四种情况,删除*,删除*及之前一个字母,*重复一次和*重复两次。

Leetcode  (hard)  10. 正则表达式匹配

c++解答

3.1.14  最长递增子序列

给定一个无序数组,求其中最长递增子序列的长度。

动态规划,记dp[i]为从数组开始到第i个元素的子序列中,最长的递增子序列长度。dp[0]=1,dp[i]=max(dp[j]+1) 0<=jnums[j],那么到第i个元素的最长子序列长度=dp[j]+1,并且从中选择最大值。如果没有nums[j]

Leetcode  (medium)  300. 最长上升子序列

c++解答

3.1.15  零钱兑换

给定一个零钱数组,每个零钱的数目均为无限,使用其中的零钱兑换给定的面值,要求使用零钱的数目最少。

动态规划问题,记dp[i]为面额i的最少零钱数目,不能兑换的金额dp[i]=-1,那么dp[i]=min(dp[i-coins[j]]+1), dp[i-coins[j]]!=-1。要注意不能利用贪心思路,最后一次取面额最大的,考虑1 5 6零钱和金额10,如果利用贪心思路,最后一次取6,那么组合为6 1 1 1 1,而最优解为5 5。所以应该对每一个金额i,取所有可能的兑换方式中,使用零钱数目最少的那种。同理,不能使用回溯法,因为使用回溯法的前提是能提前退出,即贪心思路得到的解是最优解,如果不能提前退出,那么规模太大,而提前退出又不是最优解。

Leetcode  (medium)  322. 零钱兑换

c++解答

3.1.16  有冷却期的股票买卖

给定一个数组,代表每天的股票价格,可以买入卖出多次,但是每次卖出后,需要一天的冷却期,求最大利润。

记sell[i]为到第i天为止,最后一次操作为卖出的最大利润,同理cold[i]为最后一次操作为冷却期的最大利润,buy[i]为最后一次操作为买入的最大利润。有sell[i]=max(sell[i-1],buy[i-1]+prices[i]),即第i天不买不卖和卖出的最大值;buy[i]=max(buy[i-1],cold[i-1]-prices[i]),即第i天不买不卖和第i天买入的最大利润,因为买入必须在冷却期之后。cold[i]=max(sell[i-1],buy[i-1],cold[i-1]),第i-1天可以为卖出,买入和冷却期。sell[0]=0,cold[0]=0,buy[0]=-prices[0]。返回sell[n-1]即为n天彩票买卖的最大利润。

Leetcode  (medium)  309. 最佳买卖股票时机含冷冻期

c++解答

3.1.17  整数拆分

给定一个正整数n,将其拆分为至少两个数之和,使所有的因数乘积最大。

能拆3拆3,若剩下的小于等于4不再拆。因为4拆3的话是1+3,1*3=3<4。或者考虑从n=7之后,每隔3个数可以拆出一个3来,而拆3可以使乘积最大。因此dp[i]=3*dp[i-1],i>=7。

Leetcode  (medium)  343. 整数拆分

c++解答

3.1.18  单词能否由词典拆分

给定一个字符串和词典,词典中的单词可以重复使用,判断字符串能否由词典组成。

记dp[i]为s(0,i)能否被词典拆分,则如果s(0,i)在词典中,能被拆分,否则遍历j=[0,i-1],若dp[j]==true&&s[j+1,i]在词典中,那么s[0,i]能被词典拆分。

Leetcode  (medium)  139. 单词拆分

c++解答

3.1.19  组合总数

给定一个数组,从数组中任意选取数字,可重复选取,使和为目标值,求所有可能的组合数,组合数字相同但是顺序不同也视为不同组合。

dfs会超时,因为测试用例中有1,这是如果target稍大,那么dfs的规模就是target^target,开始超时还以为dfs的条件更新有问题。既然是组合类问题,都可以使用动态规划来解决。假设现在选取和为target的组合,那么最后一次选的数字p可以是数组中所有<=target的数字,如最后一次选p,那么最后一次选p的所有组合数为dp[target-p],dp[i]=sum(dp[i-p]),p<=target。当p==target时,初始条件dp[0]=1。递推到dp[target]即可,当组合数超过int型时,因为返回值为int,所以肯定是非法组合,将其dp设为0,并提前退出。

方法二:自顶向下的备忘录法。由dp[target]=sum(dp[target-p]),p=nums[i]&&p<=target,则要求dp[target],需先求dp[target-p],p=nums[i]&&p<=target,同理对于p也有这个关系。为避免递归过程中的重复求取,使用备忘录记录已经求的所有dp[i],在求dp[k]时,若未在备忘录中,求取并添加到备忘录中,否则直接查表获取。

Leetcode  (medium)  377. 组合总和 Ⅳ

c++解答

3.1.20  泰波那契数

给定一个递推关系,dp[i]=dp[i-1]+dp[i-2]+dp[i-3],dp[0]=0,dp[1]=1,dp[2]=1,求第n个泰波那契数。

使用三个int变量模拟递推过程,时间复杂度O(n),空间复杂度O(1)。

Leetcode  (easy)  5139. 第 N 个泰波那契数

c++解答

3.1.21  接雨水

给定一个数组,代表每个柱子的高度。现在从上方注水,求可以接住多少水。

对于柱子i,其能接的雨水dp[i]=min(dpLeft[i],dpRight[i])-height[i],即左右两侧最高柱子的高度中的较小者减去当前柱子的高度。对于dpLeft[i],i=0时为0,向右递推,dpLeft[i]=max(dpLeft[i-1],height[i]),即左侧那根柱子的高度和左侧那根柱子左侧所有柱子中的最大高度中的较大值。同理,dpRight[n-1]=0,向左递推,dpRight[i]=max(dpRigth[i+1],height[i]),即当前柱子右侧那根柱子的高度和那根柱子右侧所有柱子最大高度中的较大者。

Leetcode  (medium)  42. 接雨水

c++解答

3.1.22  0-1背包问题

给定n个货物,每个货物有体积和价值两个属性,给定一个容积为m的箱子,挑选货物装入箱子,求箱子中货物的最大价值。

数据结构与算法题目及C++解答_第3张图片

 记dp[i][j]为前i个货物箱子总体积为j时的最大价值,从而避免了货物的重复使用,因为货物数目和箱子总体积从0开始,所以i=[0,n],j=[0,m],但是货物索引[0,n-1]。所以有dp[i][j]=max(dp[i-1][j-weight[i-1]]+value[i-1],dp[i-1][j]) (weight[i]<=j),箱子总重量大于第i个(索引i-1)物品的体积时,第i个物品可以放入箱子,如果第i个物品放入箱子,那么箱子剩余总体积为j-weight[i],此时的最大价值+当前货物的价值value[i]即为前i个物品中放入第i个物品的最大价值,与不放入第i个物品的总价值dp[i-1][j]取最大值更新dp[i][j]。可以简化为使用两个一维数组和只使用一个一维数组来进行递推,使用两个一维数组时和使用二维数组类似,但是使用一个一维数组时,要注意内层的res[j]循环,要j递减。因为开始时,res中保存的是上一行的状态,即dp[i-1][j],每个res[j]均使用前一个即res[j-k]的值来更新最大价值,如果j递增,那么在计算res[j]时,res[j-k]已经是当前的状态了,所以会出错。而j递减,每次求res[j]时,res[j-k]都是未更新的上一行的状态。

数据结构与算法题目及C++解答_第4张图片

c++解答

3.2  贪心算法

3.2.1 跳N格子

给定一个非负数组,数组的值为当前位置可以向后跳的最大格子数,从数组开始出发,判断当前数组能否从开始跳到最后。开始使用dfs方法,但是在通过100/101个case后超时,分析dfs的时间复杂度,如果给定数组nums={9,9,9,9,9},那么dfs(1-9)->dfs(1-9)->dfs(1-9)->dfs(1-9),不能提前退出,因为dfs(1)(对第一个位置的dfs)=dfs(11)||dfs(12)||dfs(13)...,dfs(1i)代表第二层dfs对i进行dfs,那么时间复杂度就是所有数组元素大小的乘积。

正向从开始向最后推导,可能的情况太多。考虑从后向前推,如果倒数第二个能到最后一个,那么问题简化为从第一个开始能否到达倒数第二个。同理如果倒数第三个能够到达倒数第二个那么问题简化为从第一个开始能否到倒数第三个。gap初始值为1,即nums[i-2]到nums[i-1]的最小距离,如果nums[i-2]>=gap,那么gap不变,继续向前判断nums[i-3]>=gap?;如果nums[i-2]=0=gap(=2)。一直向前递推,直到第二个数组元素nums[1],此时的gap就是nums[0]到nums[size-1]所需的最小跳跃格子,如果nums[0]>=gap,即数组可以从开始跳到最后,否则数组不能从开始跳到最后。

Leetcode  (medium)  55. 跳跃游戏

c++解答

3.2.2 跳跃游戏II

给定一个一维数组,数组的值为当前位置可向后跳跃的最大距离。求从起点开始,到达终点的最少跳跃次数。从起点开始任意点可达终点。

使用贪心算法求解。从起点出发,每次都选择当前位置index的可达位置中(i=[index,index+nums[index]]),可达位置(nums[i]+i)最远的那个。如果使用从终点出发,每次都选择能够到达当前位置的位置中,最靠前的那个,时间复杂度O(n*n),因为每次都要从当前位置遍历至起点,通过91/92个测试用例后超时。

Leetcode  (hard)  45. 跳跃游戏 II

c++解答

3.2.3 加油站

有一条环形公路,给定两个数组,分别代表在当前加油站获取的油量和到达下一个加油站需要的油量。从当前加油站只能顺序前往下一个加油站,若从某个加油站出发能回到起点,则输出该加油站索引,否则返回-1。题目有唯一解,即如果能回到起点,那么起点唯一。

使用三个变量的一趟扫描算法,一个变量curGas代表当前净油量,一个变量totalGas代表总净油量,一个变量start代表起点索引。遍历两个数组,当前净油量=当前净油量+当前获取油量-当前使用油量,总净油量+=当前获取油量-当前使用油量,如果curGas<0,说明从上个起点出发,到达当前加油站,就走不下去了,因为净油量已经<0。那么令当前净油量=0,更新起点值为i+1,继续对i+1起点值进行判断,为什么start=i+1,因为走到某个加油站发现走不下去了,那么当前加油站的获取油量-当前加油站的使用油量一定<0,所以i加油站一定不能做为起点。也是因为先确定start的值,再对这个值进行判断,当前第i个发现上一个起点不可行,那么下一次判断的是以第i+1个加油站为起点。

Leetcode  (medium)  134. 加油站

c++解答

3.2.4  买卖股票I

给定一个数组,数值代表每天的股票价格,只能买入和卖出一次股票,确定买入和卖出的日期,使利润最大。

用一个变量记录买入时的价格,若当前价格比买入价格高,就更新当前最大利润;否则更新买入价格为当前值。本质上求一个子序列,使得首尾的差值最大;但是如果使用O(n*n)的暴力搜索,一定会超时。

Leetcode  (easy)  121. 买卖股票的最佳时机

c++解答

牛客网  2019校招真题  比特币最佳买卖时机

c++解答

3.2.5  买卖股票II

给定一个数组,数值代表每天的股票价格,可以多次买入和卖出,但是最多持有一只股票,且每天只能进行买入和卖出的其中一个操作,求最大利润。

用一个变量记录当前的买入价格,一个变量记录当前的最大卖出价格,若当前股票价格大于最大卖出价格,则更新最大卖出价格,否则将当前股票卖出,更新利润,准备下一次股票买入操作。

Leetcode  (easy)  122. 买卖股票的最佳时机 II

c++解答

3.2.6  回合制游戏

敌人总血量n,有两种攻击,普通攻击不需蓄力,每次扣除敌人normal点血量,蓄力攻击需蓄力一回合,每次扣除敌人buffed点血量,判断将敌人消灭的最小回合数。

这个题目可以使用dp求解,但是测试用例的n太大,因此dp超内存。dp[i]=min(dp[i-normal]+1,dp[i-buffed]+2),即最后一次攻击采用normal还是buffed来递推。使用贪心算法,当血量n<=normal时,只需一个回合。否则判断normal*2和buffed的关系,若normal*2>=buffed,则每次都使用normal攻击,否则优先使用buffed攻击,当剩余血量<=normal时,判断其是否>0,若大于0,最后一次攻击使用normal,否则最后一次敌人已经死亡。

牛客网  拼多多2019校招真题  回合制游戏

c++解答

3.2.7  最低加油次数

给定一个起始油量,目标距离和k个加油站的位置和油量。求到达目的地的最少加油次数,若不能达到目的地,输出-1。

将问题转化为,当需要加油时,是否有加油站可用,若有,选择油量最大的那个,否则不能到达目的地。将油量转换为当前位置,则每个当前职位pos

Leetcode  (hard)    871. 最低加油次数    c++解答

3.2.8  分发糖果保证相邻关系

给定一个数组,代表n个小朋友的分数,要求相邻的小朋友,分数高的分的糖果比分数低的小朋友分的糖果多,每个小朋友最少分一个糖果,求最少的糖果总数。

将相邻(左右相邻)关系分开,使用两个数组,初始每个小朋友均只分配一个糖果(贪心)。先从左向右遍历,相邻小朋友分数高的分的糖果比分数低的多一个(贪心,糖果增大的幅度最小);再从右向左遍历,相邻小朋友中,分数高的比分数低的多一个糖果(贪心)。对于每个小朋友,取max(left[i],right[i])即可保证相邻关系,同时由于左右遍历时都利用了贪心,所以保证是满足关系的最小糖果分配方法。

Leetcode  (hard)    135. 分发糖果    c++解答

3.2.9 划分字母区间

给出一个字符串,将每个出现的字母都划分到一个子串中,求最大的子串划分数目及每个子串的长度。

贪心算法,记录每个字母出现的最后位置。遍历字符串,若当前索引为当前字母的最后出现位置,则将start和end划分为一个子串,并移动start到下一个起始点,否则更新最大end。

Leetcode  (medium)  763. 划分字母区间    c++解答

4.数据结构

4.1 链表

非顺序储存结构, 其每个节点在内存中的地址可以不连续,顺序访问结构,通过当前节点只能访问下个节点。只能保存头节点以及连接关系,如果要访问尾结点,需要从头节点开始遍历,直至尾结点。

4.1.1 交换链表节点

注意当使用链表节点的副本进行交换时,会改变原链表头的位置。因此创建一个前驱节点做为链表头,并使用其副本进行前驱节点pre、当前节点cur、后驱节点nxt之间的操作。1、保存cur的后驱节点,防止修改改cur->next之后断链 2.修改cur->next=cur->next->next 3.修改前驱节点的连接关系 pre->next=cur 4.修改后驱节点的连接关系 nxt->next=cur 5.保存下次操作的前驱节点 pre=cur 6.当前节点移动至下一轮交换的起点 cur=cur->next

数据结构与算法题目及C++解答_第5张图片

Leetcode (medium) 24. 两两交换链表中的节点

c++解答

4.1.2 删除链表倒数第n个节点

使用三个指针分别记录快指针,当前节点,当前节点的前驱节点,起点均为链表头。快指针每次向后移动一次,当快指针移动n次时,下次开始移动当前节点;当当前节点移动一次之后,下次开始移动前驱节点。这样当快指针到达链表尾时,当前节点停在链表的倒数第n个节点,前驱节点停在倒数第n个节点的前一个节点。如果当前节点停在链表头,说明此时没有前驱节点,直接返回当前节点之后的链表即可;否则将前驱节点连接到当前节点的下个节点。

Leetcode (medium) 19. 删除链表的倒数第N个节点

c++解答

4.1.3 合并K个排序链表

取k个链表当前节点中的最小值作为合并后链表的当前节点,使用的那个链表节点向后移动,知道数组中的所有链表都到达链表尾。当数组中所有链表都到达链表尾,即等于nullptr时,合并结束。因为是判断k个数字中的最小值,最好使用小根堆,但是stl中的priority_queue对ListNode结构判断大小似乎有问题,需要自己重写比较函数。因此这里直接使用k个数字比较大小的方法,时间复杂度不佳。

Leetcode  (herd)  23. 合并K个排序链表

c++解答

4.1.4 合并两个排序链表

Leetcode  (easy)  21. 合并两个有序链表

c++解答

4.1.5 删除链表重复元素

Leetcode  (easy)  83. 删除排序链表中的重复元素

c++解答

4.1.6 旋转链表

给定一个链表,求向右旋转k次后的链表。根据旋转的定义,记n为链表节点个数,当k整除n时,链表旋转后的结果与未旋转相同,因此旋转k%n次即可,又旋转k次的结果为,头节点后移k次,将前面的节点放到链表尾。即k次循环,当前头作为链表尾,当前头向后移动一次。

Leetcode  (medium)  61. 旋转链表

c++解答

4.1.7  删除排序链表中的重复元素II

给定一个链表,链表中的数值为升序,且有重复数值。将链表中出现次数超过1次的数字全部删除,仅保留出现一次的数字。

使用两个指针pre和cur,当pre的值不等于cur的值时,将pre放入新链表,否则cur后移直至cur的值不等于pre的值,然后pre=cur,cur再后移。这样pre的值与上一个重复的pre值不同,否则,cur后移到了与pre不同的值,但是pre仍为上个重复的值,此时pre->val!=cur->val,会将错误的重复数值添加到新链表中去。另外,一个易错点是判断节点的值首先要判断节点是否是空指针,判断节点的下个节点先要判断链表非空。一个边界条件是,当cur是链表最后一个节点(非链表尾nullptr),且不与pre的值相等时,应该添加到新链表中。否则while(cur!=nullptr)会跳过最后一个节点。

Leetcode  (medium)  82. 删除排序链表中的重复元素 II

c++解答

4.1.8  分隔链表

给定一个链表和一个数值x,要求将链表中所有小于x的节点放到链表前面,大于等于x的节点放到链表后面,且维持节点间的相对顺序。

使用两个节点分别构建新链表前半部分和后半部分,一趟扫描的O(n)算法。遍历原链表,如果当前节点的值小于x,放到pre节点的后面,否则放到last节点的后面,最后将pre节点连接last节点即可。注意链表的伪头和头,遍历完原链表后,pre伪头位于前半部分链表的最后一个节点,此时将pre指向lastHead->next(因为创建时多创建了一个无用节点),即可将preHead的尾与lastHead连接起来,返回时返回preHead->next,同样跳过创建preHead时创建的无用节点。

Leetcode  (medium)  86. 分隔链表

c++解答

4.1.9  反转链表II

给定一个链表,要求反转第m个到第n个链表节点,使用一趟扫描算法。

使用一趟扫描算法即不能先遍历链表,确定m-n位置的值,再修改。使用头插法反转链表可以实现一趟扫描。m-n链表将原链表分为三部分,L1为m之前不需要反转的链表,L2为m到n需要反转的链表,L3为n+1至原链表尾不需反转的链表。遍历原链表,当m<=i<=n时,使用头插法反转L2。需要记录L1的尾,L2(反转后的链表)的头和尾,L3的头,反转后L1的尾连接到L2的头,L2的尾连接到L3的头即可。若m==1,那么L1的尾不存在,返回L2的头,否则返回原链表头。

Leetcode  (medium)  92. 反转链表 II

c++解答

4.1.10 判断两个链表的交点

给定两个链表,判断其是否有交点。

若链表a的长度为la,链表b的长度为lb,那么从a出发遍历a和b以及从b出发遍历b和a的次数相同,若其中有交点,则第一个相同的节点必为交点。否则两个链表均停止在对方的尾nullptr。链表节点==的要求是 val相同,next相同。另外,当遍历完一个链表去遍历另外一个链表时,注意先判断nullptr再跳过,否则while循环无法退出(a和b同时为nullptr退出),而且每一轮a和b只能移动一次,要么向后移动(当前不等于nullptr),要么从对方链表头开始遍历(当前等于nullptr)。

Leetcode  (easy)  160. 相交链表

c++解答

4.1.11  移除链表元素

给定一个链表和一个目标值,将链表中等于目标值的节点删除。

先判断新链表的头,遍历链表找到第一个不等于目标值的节点,作为返回链表的头。从该位置开始向后遍历,记录前节点和当前节点,若当前节点等于目标值,一直向后遍历,直至当前值不等于目标值或到达链表尾。将当前节点连接到前节点的后面,更新前节点为当前节点,更新当前节点向后继续遍历。

Leetcode  (easy)  203. 移除链表元素

c++解答

4.1.12  删除链表节点

给定链表的一个节点,从链表中将其删除。给定的节点不会是首尾节点。

ListNode* node对于ListNode相当于传值,因此任何对node的赋值操作只在当前函数中有效,所以不能用node=new ListNode的形式来删除当前节点。但是对于ListNode的成员val和next来说是传址传递,因此在函数中修改val和next为下个节点的val和下个节点的next即可。

Leetcode  (easy)  237. 删除链表中的节点

c++解答

4.1.13  环形链表的入口

给定一个链表,判断其是否有环,如果有环,找到环的入口。

哈希表解法。时间复杂度O(n),空间复杂度O(n),遍历链表,将第一次出现的节点放入哈希表中,第一个出现两次的节点即为环的入口,否则会到达链表尾。

双指针解法。快慢指针从头节点出发,快指针每次走两步,慢指针每次走一步,若有环快慢指针一定会再相遇。若相遇,将慢指针从头出发,与快指针每次均走一步,第一个相遇的节点即为环的入口节点。

Leetcode  (medium)  142. 环形链表 II

c++解答

4.1.14  反转链表

给定一个链表,将其节点反转。

1.头插法。一个指针记录当前节点,顺序遍历原链表;一个指针记录前节点的位置,作为当前节点的下个节点实现反转。

2.使用栈,全部节点入栈后,依次取出重新构建链表即可。

Leetcode  (medium)  206. 反转链表

c++解答

4.1.15  奇偶链表

给定一个链表,将链表的奇序节点放到前面,偶序节点放到后面。头节点的序号为1。

奇序节点的下个节点为当前节点的下两个节点,偶序节点的下个节点为当前节点的下两个节点,因此不会修改对方的节点,所以不需保存。当修改完后,奇序节点位于最后一个奇序节点,要把该节点连接到偶序节点的起始。但是因为修改了头节点的下一个节点(偶序节点的起始节点),所以要提前保存该节点。

数据结构与算法题目及C++解答_第6张图片

Leetcode  (medium)  328. 奇偶链表

c++解答

4.1.16  链表形式的十进制数字求和

给定一个两个链表,链表头为低位数字,求两个链表所表示的十进制数字之和,结果仍按低位在前的形式返回链表。

遍历两个链表,当前节点之后+进位对10取余作为当前位的值,和除以10为进位值,按照低位在前构造链表即可。注意最后,若进位不为0,需要新增一个节点1。

Leetcode  (medium)  2. 两数相加

c++解答

4.2 字符串

4.2.1 最长公共前缀

公共前缀的最好办法是Trie前缀树,但是这个题目可以使用简单办法,构建Trie树也需要大约80Lines+代码,所以使用字符串操作即可。最长公共前缀的长度为字符串数组中长度最小的字符串的长度。所以先找出这个字符串str,然后利用一个数组arr记录该字符串每一位字符的公共个数,初始值为数组字符串个数size,当有一个字符串的该位字符与最小字符串该位字符不同时,记录该位相同个数size-1。最后遍历这个数组,找出最后一个大小为size的索引,输出最短字符串从开始到该索引的字符串即可。

Leetcode 14. 最长公共前缀

c++解答

4.2.2 大数相加

给定两个字符串形式的十进制数字,求其和。模拟加法,注意末位(最低位)对齐和进位。

Leetcode  (easy)  415. 字符串相加

c++解答

4.2.3 大数相乘

给定两个字符串表示的十进制数字,使用字符串操作的方式求它们的乘积。

根据乘法公式的定义,num1*num2=num1*(num2[0]+num2[1]+...+num2[size-])。定义m_pre为前面所有乘积的和,m_cur为当前位乘积,定义两个函数,一个函数计算整数与0-9的乘积,一个函数计算等长度的整数加法。将num1乘num2[size-1]的乘积作为起始的m_pre,对num2[i]的每一位,模拟乘法表达式的过程,注意前面的乘积之和与当前乘积需要对齐,对齐方式是,当前乘积的最后一位与上个乘积的倒数第k位对齐,k=2,3,...,size-2。因为当计算num1*num2[size-2]时,此时的m_cur的最后一位与m_pre的倒数第二位对齐,同理m_cur=num1*num2[size-3]时,m_cur最后一位与m_pre倒数第三位对齐。

数据结构与算法题目及C++解答_第7张图片

Leetcode  (medium)  43. 字符串相乘 

c++解答

4.2.4 最后一个单词的长度

给定一个由小写字母和空格组成的字符串,求最后一个单词的长度。从后向前遍历,找到第一个字母开始计数,直到遇到空格或到字符串开始。

Leetcode  (easy)  58. 最后一个单词的长度

c++解答

4.2.5 二进制加法

给定两个字符形式的二进制数字,以求其字符串形式的和。 模拟二进制加法,末位对齐,注意索引和进位即可。为了明显减少代码量,将a作为长度较长的字符串,b作为长度较短的字符串。剔除else情况下的逻辑相同仅字符串名称交换的代码。

Leetcode  (easy)  67. 二进制求和

c++解答

4.2.6  字符串包含

给定两个字符串s1和s2,判断是否有包含关系。

s1的子串==s2,称s1包含s2;s2的子串==s1,称s2包含s1。因此两种情况都需要判断。因为测试用例较简单,不需要使用kmp算法。以较短的字符串长度分割较长的字符串,判断每个子串是否与较短的字符串相同即可。

牛客网  2019校招真题  字符串包含

c++解答

4.2.7  字符串压缩

给定一个字符串,将重复字母打印为字母+出现次数。输出压缩后的字符串。

以char型变量c记录当前字母,以int型变量cnt记录当前字母出现的次数,初始值c=s[0],cnt=1。从s[1]开始遍历字符串,若s[i]!=c,打印c及计数值,并更新c=s[i],cnt=1;否则增加cnt计数值;循环退出后打印最后一个c及其计数值即可。

牛客网 2019校招真题  字符串压缩

c++解答

4.2.8  字符串长度最大乘积

给定一个字符串数组,求其中任意两个出现字母不重复的字符串长度最大乘积。

模拟过程。每个字符串i与[i+1,n-1]比较。

牛客网  2019校招真题  字符串长度最大乘积 

c++解答

4.2.9  有效数字

给定一个字符串形式的数字,判断其是否有效。输入有数字,正负号 空格 e 小数点。

根据正负号、e、小数点与数字的关系,如e之前之后必须有数字,小数点后必须有数字,正负不在首位则其前面必须为e等确定是否为有效数字。

Leetcode  (hard)  65. 有效数字

c++解答

4.3 栈、队列和堆

栈,stack,LIFO (last in first out)后入先出(或者叫先入后出),先放入的值在栈底,后放入的值在栈顶。只能访问栈顶元素。

队列,queue,FIFO(first in first out),先入先出,先入队列的元素在队列首,后入队列的元素在队列尾,只能访问队列首元素。

堆,stack(又称优先队列priority_queue),分为大顶堆和小顶堆,只有堆顶元素为堆中所有元素的最大或最小值,只能访问堆顶元素。

4.3.1 括号字符串匹配

给定一个由左右大中小括号组成的字符串,判断括号是否匹配。遍历字符串,左括号入栈,右括号取栈顶,匹配则继续判断;栈空或不匹配则字符串不匹配,退出。

Leetcode 20. 有效的括号

c++解答

4.3.2 括号字符串中最大匹配子串的长度

给定一个由'(' ')'组成的字符串,求其中最大匹配子串的长度。使用栈可以判断子串的匹配是否合法,但是在合法子串之间的非法括号无法区分,因此栈中元素既要有字符('('还是')')也要有字符在字符串中的位置(索引)。有两种方法可以获取最大子串的长度,一是对所有匹配的字符,记录索引,在匹配结束后,统计栈中不匹配的左括号的索引,做差求最大子串的长度。二是将原字符串中所有匹配的括号子串均修改为其他字符,如'*',这样在遍历字符串每个字符后,统计字符串中最长的'*'子串的长度即可。时间复杂度O(n+n),题目给的原形为string,如果不能修改为string&,那么需要额外的空间来保存字符串并修改,空间复杂度O(n);若可以自行修改为string&,那么空间复杂度为O(1)。

Leetcode  (hard)  32. 最长有效括号 

c++解答

4.3.3 解析布尔表达式

给定一个字符串形式的布尔表达式,有复合逻辑运算,求其逻辑运算结果。

因为有复合运算(括号复合),所以内部括号的运算优先级高于外部括号。使用一个栈来储存当前的运算符和元素,知道遇到')',将一个子逻辑运算符从栈中取出,计算结果并入栈,这样遍历表达式后,栈中的元素即为表达式的运算结果。

Leetcode  (hard)  1106. 解析布尔表达式 (周赛题目 题库暂无)

c++解答

4.3.4  计算逆波兰表达式

给定一个字符串形式的逆波兰表达式,求其值。

非运算符,求字符串转int并入栈,遇到算术运算符,取出栈顶和次栈顶元素执行相应操作,计算结果再入栈。注意+和*对操作数没有顺序,但是-和/,因为使用栈,所以除数和减数在后,被除数和被减数在前。

Leetcode  (medium)  150. 逆波兰表达式求值

c++解答

4.3.5  最小栈

设计一个栈,实现入栈,查询栈顶,查询最小值和出栈操作。

因为设计的是栈的功能,所以实现栈的数据结构来实现。要实现的是在栈的标准操作上,增加获取当前最小值的操作。因此使用两个栈,一个栈实现栈的标准操作,另一个栈记录当前的最小值。入栈:标准栈入栈;当最小栈空时,直接入栈,否则比较当前元素与最小栈栈顶元素,小于等于(防止标准栈多个重复元素,而最小栈只保存一个,此时标准栈出栈一个,而最小栈空的情况)最小栈顶元素时,入栈。出栈,比较标准栈与最小栈栈顶元素,若相等,同时出栈,否则只出栈标准栈。返回栈顶:返回标准栈栈顶元素;返回最小值:返回最小栈栈顶元素。

Leetcode  (easy)  155. 最小栈

c++解答

4.3.6  实现加法减法计算器

实现加法减法和括号表达式的运算,并且字符串中可能有空格。

标准解法:1.分割各操作数 2.中缀转后缀 3.计算后缀  这个题目因为只有加法减法可以用栈来计算,非标准解法,利用栈来解决()包含问题,计算每一个没有括号的基本加减法,结果放入栈中。注意要逆序栈中出栈的表达式。

Leetcode  (hard)  224. 基本计算器

c++解答

4.3.7 用队列实现栈

使用队列来实现栈的功能,包括push,pop,top,empty。

使用一个队列,每次push都将队列重新排列成,队首为后入队列元素。

Leetcode  (easy)  225. 用队列实现栈

c++解答

4.3.8  实现无括号的加减乘除运算

给定一个加减乘除的算术表达式,求其值。

标准解法:1.分割各操作数 2.中缀表达式转后缀表达式 3.计算后缀表达式  注意优先级 栈中的+-比当前的加减先出栈,不比当前乘除先出栈。栈中的乘除比当前的所有运算符都先出栈。

Leetcode  (medium)  227. 基本计算器 II

c++解答

4.3.9  用栈实现队列

使用栈来实现队列,push,pop,peek,empty操作。

两次先入后出会恢复先入先出的顺序,栈1保存入队列顺序,当pop或peek时,查询栈2是否空,若空将栈1的内容顺序放入栈2,这样栈2的顶是先入队列的元素。

Leetcode  (easy)  232. 用栈实现队列

c++解答

4.4.10  数组中第k个最大元素

给定一个未排序数组,求降序排序后,第k个元素。

数组全部入大顶堆,出堆k-1次,则堆顶元素即为所求元素。

Leetcode  (medium)  215. 数组中的第K个最大元素

c++解答

4.4.11  前k个高频元素

给定一个无序数组,返回其中出现次数最高的前k个元素。

遍历数组,记录每个数字出现次数,以每个数字出现次数作为比较函数,构建小顶堆,所有数字入堆。堆顶出堆,直到堆中元素的个数为k,这样堆中剩余的就是出现次数前k高的元素。将堆中元素放入结果即可。

Leetcode  (medium)  347. 前 K 个高频元素

c++解答

4.4.12  柱状图中的最大矩形

给定一个柱状图,求其中能组成的最大矩形面积。

使用单调栈,当柱子的高度小于栈顶时,取栈顶和次栈顶柱子求最大面积并更新最大面积,若无次栈顶的柱子时,当前面积=栈顶柱子的高度*其索引,否则为栈顶柱子的高度*(i-s.top()-1),直到栈顶柱子的高度小于当前柱子,当前柱子入栈。为保证最后一根柱子入栈后能求面积,在数组最后添加0。

Leetcode  (hard)    84. 柱状图中最大的矩形    c++解答

4.4.13  矩形区域内的最大矩形面积

给定一个矩形区域,由0和1组成,求由1组成的最大矩形面积。

行遍历矩形,以当前行的每一列更新柱状图的高度,若当前为1,那么当前列的柱子高度+1,否则当前列的柱子高度为0。以每一行构建柱状图,求最大矩形面积,并更新最大矩形面积。

Leetcode  (hard)    85. 最大矩形    c++解答

4.4.14  矩形区域的最大矩形

给定一个矩形区域,空地为1,障碍物为0,求空地上能否放置一个cxd大小的箱子。

同4.4.13,但是每行的柱状图求面积时,返回值为最大面积,长,宽。因为未规定长宽的大小,因此将最大矩形的长宽与箱子的长宽对齐,只要c<=lengthMax,d<=widthMax即能放置。

c++解答

数据结构与算法题目及C++解答_第8张图片

4.4 数组和矩阵

4.4.1 删除排序数组中的重复项

给定排序数组,将所有不重复的元素按照顺序放到数组前部,要求O(1)的空间复杂度。

方法一:遍历数组,使用一个变量pre记录前值,对于所有数组的数字cur,如果小于等于前值pre,则向后寻找第一个比前值大的元素nxt,修改当前数字cur=nxt,并更新前值pre=nxt。如果比前值大,则修改前值pre=cur。时间复杂度是O(n*n),运行时间300ms,效果不是很好。

方法二:使用两个索引,index代表前面不重复数字中待修改的位置,i对数组进行遍历。如果arr[i]!=arr[i-1]那么就将arr[i]的值赋给arr[index],同时index+1指向不重复数字中下一个待修改的位置。时间复杂度O(n),运行时间30ms,比O(n*n)要好很多。

Leetcode 26. 删除排序数组中的重复项

c++解答

4.4.2 实现strStr()

给定两个字符串,求str2在str1中的位置。暴力法:对于每一个str1的字符,若等于str2[0],向后取字符构建长度等于str2的字符串并与str2比较,时间复杂度O(n*n);KMP法:解决暴力法中的回溯问题,时间复杂度O(n*m)。当匹配不成功时,将匹配起点在str1中向后移动一位,而是通过构建str2的匹配表(前i位中,前缀子集和后缀子集相同的最大字符串长度),通过str2进行回溯,跳过在上一次匹配中已经发现的匹配的位。假设str1从i位开始与str2匹配,到str2的第j位发现与str1的ii位不同;此时不是将i从i+1开始继续上述匹配过程,因为计算了str2中前缀后缀相同的最大长度,那么在前面的匹配中,根据前缀后缀表,必有i的前几位与j的前几位相同,那么下次判断直接跳过这几位即可。暴力法用时120ms,KMP法用时8ms。另外附上KMP算法的C++实现,可用于求str1中第一个str2或者所有str2,方法为以str1的索引i为循环结束条件,当j=str2.size()时,说明找到匹配的str2,此时的i-j为匹配的str2首个字符在str1中的位置,记录i-j,令j=0继续循环。虽然题目只有easy难度,但是如果是考察KMP算法,而不是简单的O(n*n)就可以的话,KMP算法的理解还是要一番功夫的。

Leetcode  (easy)  28. 实现strStr() 

c++解答

KMP算法c++实现

4.4.3 下一个排列

给定一个数组,求其所有组合中,从小到大排序,其后的那个组合。如输入132,则输出213。对于输入数组nums,从size-2开始向前判断每一个nums[i],其后的数字中,比其大的数字中的最小值,交换这两个数字可使当前位最小。再对交换后数组中nums[i]之后的所有数字进行增序排序即可。如输入13254,那么交换nums[2]=2和nums[4]=4得13452,保证高位nums[2]是所有其之后的数字中最小的。再对i>2之后的所有数字排序即可。

Leetcode 31. 下一个排列

c++解答

4.4.4 有效的数独

给定一个9x9的矩阵,判断是否为数独。有三个要求,每一行中没有重复数字,每一列中没有重复数字,并且矩阵的每一个分块3x3矩阵中没有重复数字。将矩阵按行、列、三宫格区分,建立一个哈希表并判断是否有重复元素。注意对3宫格的检测,使用左上角索引进行3x3检测,并更新左上角索引,因为for循环嵌套没有使用声明初始化,所以内层循环每次要赋初值,否则内层循环只对第一个外层循环索引i有效,i之后的索引,内层循环的j都已满足内层循环结束条件。

Leetcode  (medium)  36. 有效的数独

c++解答

4.4.5 最大子序和

给定一个数组,数组元素有正有负,求其子序列中,子序列和最大的那个值。遍历到数组元素nums[i]时,使用一个变量sum记录之前的元素之和,当前sum=sum+nums[i],若当前和sum<0,那么遍历下个元素时,数组的最大和子序列一定不包含sum,所以令sum=0。

Leetcode  (easy)  53. 最大子序和

c++解答

4.4.6 区间合并

给定一个数组,数组中的元素为区间的起点和终点,将数组中的所有重复区间合并,并输出合并后的区间值。如[1,3] [2,4]可以合并为[1,4],[1,3] [3,5]可以合并为[1,5]。将区间按照起始值排序,以一个变量记录当前区间cur,向后遍历数组中每个区间值,如果当前区间nums[i][0]<=cur[1],即数组当前区间的起始值小于当前区间的终点值,那么说明可以合并。合并后的新区间为[cur[0],max(cur[1],nums[i][1]),因为数组区间经过排序,cur的起始值一定小于等于nums[i]的起始值,但是终点值没有排序,所以要取两个区间中的最大值。

Leetcode  (medium)  56. 合并区间

c++解答

4.4.7  矩阵置零

给定一个二维矩阵,将0在的行和列置为0,尽量优化空间复杂度。遍历数组在发现0后,不能立即将0在的行列置0,因为这样会修改还未遍历的位置,当下次遍历时,这个0是数组原来的0还是修改后的0无法分辨。使用一个矩阵记录该0是否是被修改的需要o(m*n)的空间复杂度,使用两个变量记录0在的行和列需要O(m+n)的空间复杂度。常数空间复杂度O(1)留待解决。

Leetcode  (medium)  73. 矩阵置零

c++解答

4.4.8 二维行列有序矩阵搜索

给定一个二维行列有序矩阵,每一行从左向右数字增大,每一列从上往下数字增大。给出一个target值,求该值是否在矩阵中。 遍历矩阵,O(m*n)的时间复杂度,不是最优。考虑从矩阵左上角和右下角出发,此时target与当前元素值得大小与下个移动方向之间有唯一对应关系,比当前值大则向下,比当前值小则向左,时间复杂度最大O(m+n)。

Leetcode  (medium)  74. 搜索二维矩阵

c++解答

Leetcode  (medium)  240. 搜索二维矩阵 II

c++解答

4.4.9 区间插入

给定一个数组表示的有序区间,数组内区间已不可合并。给定一个新的区间,将该区间插入到区间数组中,如果有重叠则进行合并。

遍历数组,若新区间的起点大于当前区间的终点,则向后继续遍历,否则将新区间当前区间的前面,这样操作后新区间在区间数组中能合并的第一个区间之前,记录其位置start。再遍历原数组,如果index

Leetcode  (hard)  57. 插入区间

c++解答

4.4.10  螺旋矩阵II

给定一个方阵的行列数,返回该矩阵,要求矩阵中的元素顺时针赋值1~n*n。

每一轮是一个回字型,修改两行两列,使用n作为剩余待修改的行列数,每一轮的修改过程是右下左上。注意for循环后,索引超出范围需要复原,以及后面的遍历不要重复修改前面遍历的值即可。

Leetcode  (medium)  59. 螺旋矩阵 II

c++解答

4.4.11  螺旋矩阵

给定一个mxn矩阵,顺时针方向遍历矩阵中的每个元素,返回遍历顺序。

每一轮从对角线上的点出发顺时针遍历两行两列(右下左上),更新剩余待遍历的行数和列数,注意不要重复遍历。因为每一轮至少处理两行两列,否则会重复添加,因为需要对剩余行<2以及剩余列<2的情况单独处理。

Leetcode  (medium)  54. 螺旋矩阵

c++解答

4.4.12  杨辉三角

给定一个整数n,生成对应的杨辉三角矩阵。

每一行的第一个和最后一个数字为1,从第3行开始,每一行的第2个到倒数第2个数字,每一个都是上一行对应两个数字之和。用一个数组记录上一行的值,更新当前行即可。

Leetcode  (easy)  118. 杨辉三角

c++解答

4.4.13  杨辉三角II

给定一个整数k,输出杨辉三角的第k行(k从0开始)。要求空间复杂度O(k)

在上个题目中,使用了一个数组来保存上一行,这样空间复杂度是O(2*k)。所以考虑只用一个数组的解决方法。模拟递推过程,数组中的当前数字为上一行的杨辉三角。所以res[i]=res[i]+res[i-1],但是这样res[i]的值会变,而求res[i+1]时,又要用到res[i],所以使用一个变量pre代表上一行杨辉三角中的上一个数字res[i-1],用一个变量先保存当前的res[i]值,res[i]=res[i]+pre,更新完当前值后,再令pre=temp,继续 下次修改。第k行,要修改的数字为数组索引从1到k-1的数组元素(此时k代表数组中有k+1个元素),所以k从2开始。

Leetcode  (easy)  119. 杨辉三角 II

c++解答

4.4.14  反转字符串中的单词

给定一个字符串,字符串中得每个单词由空格分隔,反转其中的单词顺序,将后出现得单词放到字符串前面,且多个空格只保留一个。

从后想前遍历字符串,用一个字符串temp保存当前单词,如果遇到空格且temp非空,那么反转temp,放入结果res,并添加一个空格(消除多个空格,只保留一个)。另外当遍历到字符串第一个字符,又不是空格时,也要将temp反转,放入res。这样反转后的字符串中,最后一个单词可能是由第一种情况放入的,此时反转字符串末尾会多出一个空格,将其删去。

Leetcode  (medium)  151. 翻转字符串里的单词

c++解答

4.4.15  旋转矩阵

给定一个数组,要求旋转k次,每次旋转将数组首的元素放到数组尾。

设数组大小为n,那么每旋转n次,数组复原。因此只需要旋转k%n次即可。如果每次都执行将数组首的元素放到数组尾,那么时间复杂度为O(n),实际执行时不佳。考虑局部反转使整个数组反转。需要旋转k次即将数组前size-k个元素顺序放到数组尾,因此将数组前size-k个数字反转,将数组后k个数字反转,再将整个数组反转,即可得到旋转后的数组,时间复杂度O(logN)。

Leetcode  (easy)  189. 旋转数组

c++解答

4.4.16  比较版本号

给定两个字符串形式的版本号,判断其大小关系。

以' . '分割版本号的每一位,由高位开始,判断每一位版本号字符串对应数字的大小关系。直到有一个字符串版本号到达结尾,若两个版本号均到达结尾,说明版本号相等。否则再判断未到结尾的那个版本号,剩余的每一位版本号是否均为0,若有一位不为0,则说明该版本号大于已到达结尾的那个版本号,若均为0,则两个版本号相等。

Leetcode  (medium)  165. 比较版本号

c++解答

4.4.17  汇总区间

给定一个不含重复数字的有序数组,每个连续区间只保留起点和终点值,求所有区间。

两个变量分别记录当前区间的起点和终点,遍历数组。当当前值大于当前区间终点值+1时,意味着上个区间已达到终点。退出后,最后一个区间也放入结果。

Leetcode  (medium)  228. 汇总区间

c++解答

4.4.18  除自身以外数字的乘积

给定一个数组,求除当前数字之外所有数字的乘积。要求时间复杂度O(n),且不能使用除法。

从左往右遍历,再从右向左遍历,两次遍历错开。

原始数组 a                     b              c                d
左遍历 1                     a              a*b          a*b*c
右遍历 d*c*b              d*c            d               1
乘积 1*d*c*b        a*d*c        a*b*d        a*b*c*1

Leetcode  (medium)  238. 除自身以外数组的乘积

c++解答

4.4.19  顶端迭代器

给定一个迭代器类的两个方法,next()返回迭代器的值,且迭代器移动到下一个位置,hasNext()判断当前迭代器位置其后是否还有元素。定义一个子类,继承自该迭代器类,并定义next(),hasNext(),和peek()方法,peek()方法只返回迭代器的值而不移动迭代器。

peek()方法使用当前迭代器的副本,先判断是否到达终点,若不是终点,返回当前值。

Leetcode  (medium)  284. 顶端迭代器

c++解答

4.4.20  生命游戏

给定一个m*n数组,0代表细胞死亡,1代表细胞存活。若活细胞周围活细胞数目==2或3,继续存活,否则死亡。若死细胞周围有3个活细胞,死细胞复活。周围的定义是9宫格中周围的8个格子。

使用一个数组作为更新状态,因为细胞死亡的条件更多,所以初始化为全部细胞死亡。按照题意统计每个细胞周围的活细胞数目并更新即可。

Leetcode  (medium)  289. 生命游戏

c++解答

4.4.21  区域检索求和

给定一个数组,设计一个函数sumRange(i,j),求[i,j]所有元素之和。该函数会被多次调用,数组保持不变。

因为sum函数会被多次调用,而数组又保持不变。因此每次都遍历求和显然效率很低。考虑sum(i,j)=sum(0,j) (i==0),sum(i,j)=sum(0,j)-sum(0,i-1)(i>=1);因此使用一个数组sum来保存从数组第0个元素开始到第i个元素之和,构造该数组需要O(n)的时间复杂度,但是之后每次调用sumRange()只需要对该数组使用索引访问,时间复杂度为O(1)。比每次调用都遍历[i,j]求和效率高很多。

Leetcode  (easy)  303. 区域和检索 - 数组不可变

c++解答

4.4.22  二维区域检索求和

给定一个数组,设计一个函数sumRange(r1,c1,r2,c2),求矩形区域(r1,c1)-(r2,c2)之间所有元素的和。该函数会被多次调用,数组保持不变。

同上一个题目。保存前缀和用于求和,其中sums[i][j]代表第i行,第0至第j个元素之间所有元素的和。sumRange(r1,c1,r2,c2)=sum(r1,c1,c2)+sum(r1+1,c1,c2)+...+sum(r2,c1,c2),其中sum(i,c1,c2)=sum[i][c2](c1==0),sum(i,c1,c2)=sum[i][c2]-sum[i][c1-1](c1>=1)。

Leetcode  (medium)  304. 二维区域和检索 - 矩阵不可变

c++解答

4.4.23  矩阵中的路径

给定一个字母板,左上角为'a',共6行5列,其中最后一行只有一列。给定一个小写字母组成的字符串,起点为左上角元素'a',求移动顺序,来得到给定的字符串。

以起点字母和终点字母为角点的矩形,沿任意两条边移动为最短路径。要注意'z',z字母只有u可以到达,所以如果起点为z,需要先移动到u,如果终点为z,需要先从起点移动到u。

Leetcode  (medium)  5140. 字母板上的路径

c++解答

4.4.24  矩阵中以1为边界的最大正方形

给定一个矩阵,矩阵元素为0或1。求矩阵中以1为边界的最大正方形,其包含的元素个数。

使用两个数组dp1,dp2分别记录从矩阵元素grip[i][j]出发,作为左上角点和右下角点的最大边长。以grid[i][j]为正方形左上角点,则右下角点为grip[i+k-1][j+k-1],令k从dp1[i][j]开始递减,若dp2[i+k-1][j+k-1]>=k,说明当前可构成正方形,又因为边长递减,所以第一个可构成的正方形一定是以grip[i][j]为左上角点的最大正方形,遍历矩阵元素求最大正方形的边长,其元素个数为边长的平方。

Leetcode  (medium)  5141. 最大的以 1 为边界的正方形

c++解答

4.4.25  安置路灯

给定一个数组代表道路,其中'.'表示需要被路灯照亮的地方,'X'代表不需要被路灯照亮的地方。每个路灯可以照亮安装位置及其左右的位置。求最少需要安装多少路灯。

从道路起始开始,遇到'.'则安装一个路灯,并跳过3个位置,到下一个位置继续判断。因为只要位置i有'.',则i,i+1,i+2中必有一个位置需要安装路灯,而不管剩下两个位置需不需要照亮,都可以通过安置一个路灯来解决,如.X.,..X都可以在中间位置安置路灯。注意循环中,j在循环体的{}结束后会+1,因此循环体中j+=2即可在下次循环时,从其后的第三个位置开始。

牛客网 2019校招真题    安置路灯    c++解答

4.4.26  迷路的牛牛

给定一串字母,L代表左转,R代表右转,起始朝向N,求最后的朝向。

1.左右转相互抵消2.同方向旋转次数对4取余

牛客网  2019校招真题    迷路的牛牛    c++解答

4.5 哈希表

4.5.1 判断单词的字母是否相同

给定一个字符串数组,将其中单词相同的字符串放入同一个数组中,并最后返回分组后的字符串数组。若两个字符串的单词相同,那么其ASCII排序字符串必定相同。放入组合后,对于新的字符串需要进行查找,那么使用哈希表的方式自然是最快的。使用O(n*n*26)的方法通过100/101个case后超时,而使用哈希表的方式,用时不到100ms。

Leetcode  (medium)  49. 字母异位词分组

c++解答

4.5.2  最长连续序列

给定一个无序数组,求数组中数值连续的子序列的最大长度。要求时间复杂度O(n)。

因为要求时间复杂度O(n),所以不能使用排序算法。考虑一次遍历,遍历时保存每个数字当前连续序列的长度,对于nums[i]=val,如果val-1存在且val+1存在,记left为val-1的连续序列的长度,right为val+1的连续序列的长度,那么nums[i]的连续序列长度为left+right+1,重复数字不对连续序列产生影响。修改val-1的连续序列长度为=left+right+1,修改val+1的连续序列长度为left+right+1,因为连续,所以中间的数字下次出现时不需要再修改,当val-2或val+2出现时,因此val-1和val+1的值已经修改过,所以可以递推下去。另外也要修改nums[i]的数值为left+right+1,因为val-1和val+1可能有一个或两个不存在,如果不修改nums[i],那么递推会中断。

Leetcode  (hard)  128. 最长连续序列

c++解答

4.5.3 众数

给定一个数组,求众数。

众数指出现次数>n/2的数字,遍历数组,更新当前数字出现的次数,若出现次数>n/2,即返回该数字。

Leetcode  (easy)  169. 求众数

c++解答

4.5.4  存在重复元素

给定一个数组,判断是否有重复元素。

哈希表。

Leetcode  (easy)  217. 存在重复元素

c++解答

4.5.5  重复元素的最小间隔

给定一个数组,判断重复元素的最小间隔是否小于等于k。

哈希表,value记录上次出现的索引。

Leetcode  (easy)  219. 存在重复元素 II

c++解答

4.5.6  求众数III

给定一个数组,求数组中出现次数超过n/3的数字。要求时间复杂度O(n),空间复杂度O(1)。

哈希表,暂时做到时间复杂度O(n),空间复杂度O(n)。

Leetcode  (medium)  229. 求众数 II

c++解答

4.5.7  字符串插入形成的互异组合

给定一个小写字母组成的字符串,求在任意位置插入a-z小写字母形成的互异组合总数。

插入字母c时,由于字符串中c的重复出现会导致互异组合数减小,pi=size+1-ki,其中i为0-26所代表的小写字母,size为字符串长度,ki为字母i+'a'出现的次数。求和pi即可。

牛客网  2019校招真题  游戏海报

c++解答

4.5.8 单词规律

给定一个小写字母的字符串,和多个以空格为间隔的单词。判断每个字母和单词间是否为相互匹配关系。

相互匹配关系即单映射,因此需要两个哈希表分别保存字母映射到的单词和单词映射到的字母。当两个都查询不到时,构建双向映射,两个都能查询到时,判断是否匹配,否则不匹配。

Leetcode  (easy)  290. 单词规律

c++解答

4.5.9  猜数字游戏

给定两个长度相同的由0-9组成的字符串s和g,若s和g对应位置上的数字相同,称为公牛,若不同位置上的数字相同,称为母牛,求s和g的公牛母牛数目。

第一次遍历s和g,确定公牛数目,并统计母牛数字的和个数s[n]和g[n],两个数组均为长度为10的数组,代表非公牛数字的个数。第二次遍历这连个数组s和g,统计相同数字出现次数的最小值min(s[i],g[i])。时间复杂度O(n+10)=O(n),如果使用数组记录guess中的非公牛的位置再对应去查secret中该数字出现次数并-1,那么时间复杂度为O(2n)。利用数字有界进行时间复杂度优化更佳。

Leetcode  (medium)  299. 猜数字游戏

c++解答

4.5.10  两个数组的交集-互异元素

给定两个数组,求数组的交集元素。数组中数字有重复元素。交集中每个元素互异。

利用一个哈希表O(m),两次遍历O(m+n),第一次记录数组nums1中出现的元素,若第一次出现,放入哈希表。第二次在哈希表中查找数组nums2中第一个出现的数字是否在表中,若在放入交集数组。

Leetcode  (easy)  349. 两个数组的交集

c++解答

4.4.11 两个数组交集-重复元素

同上个题目,但是每个在nums1出现的元素p和nums2中的元素p构成一个交集元素,交集数组中,必须有k个交集元素p。k为nums1和nums2中相同元素的对数。

Leetcode  (easy)  350. 两个数组的交集 II

c++解答

4.1.12  字母异位词

给定两个单词,判断其是否为字母异位词。字母异位词是指每个字母的出现次数相同的单词。

哈希表解法,遍历单词s,每个字母出现次数+1,遍历单词t,每个字母出现次数-1,最后判断哈希表中元素是否均为0即可。

Leetcode  (easy)  242. 有效的字母异位词

c++解答

4.1.13  两数之和

给定一个无序数组,从中选取两个数字,使其和为目标值。有且仅有唯一解。

使用哈希表,key值为数组元素,value为其索引。遍历数组,对于每个数字nums[i],若target-nums[i]不在哈希表中,则将nums[i]放入哈希表,否则两个数之和为target,输出两个索引。

Leetcode  (easy)  1. 两数之和

c++解答

4.6 二叉树

4.6.1 二叉树之字形寻路

给定一个二叉树,其中节点的值是之字形确定的。即奇数索引行的节点值向右递增,而偶数索引行的节点值向右递减。给出某个节点的值,求从根节点出发到该节点的路径。n

数据结构与算法题目及C++解答_第9张图片

很明显测试用例中二叉树的规模会很大,因此如果使用dfs的方法一定超时。考虑之字形和正常规则的区别。如果是正常规则,那么从根节点到k节点的路径为(k,k/2,k/4,...,1)的反转。因为偶数行的节点值的排列规则与正常规则相反,即之字形规则下,记路径上第i行的节点值为b[i],正常规则下为a[i],那么有a[i]+b[i]=m,其中m=pow(2,n)+pow(2,n+1)-1,n为比b[i]小的最大的2的幂。

因此先求正常规则下的路径,再从倒数第二个节点开始,每隔一项修改节点的值为之字形路径上对应节点的值即可。

Leetcode  (easy)  1103. 二叉树寻路  (周赛题目 题库暂无)

c++解答

4.6.2  验证二叉搜索树

给出一个二叉树,判断其是否为二叉搜索树。

二叉搜索树的定义,左子树的值小于根节点,根节点的值小于右子树,且左右子树也是二叉搜索树。因此二叉搜索树的中序遍历(左根右)是升序序列。使用中序遍历,判断当前值与上一个节点值的大小即可。注意第一个数值,即最左侧的叶节点不需判断,使用一个bool型变量标记首次访问即可。递归形式的中序遍历比迭代形式的中序遍历慢很多。建议使用迭代形式遍历二叉树。

Leetcode  (medium)  98. 验证二叉搜索树

c++解答

4.6.3  相同的树

给定两颗二叉树,判断其是否相同(结构相同且对应节点值相同)。

层序遍历,判断结构和对应节点值是否都相同,若有一不同,返回false。

Leetcode  (easy)  100. 相同的树

c++解答

4.6.4  二叉树的层序遍历

给定一颗二叉树,层序遍历返回其节点序列。

利用辅助队列,按照先左后右顺序添加当前节点的子节点,队列的当前大小即为本层的节点个数。

Leetcode  (medium)  102. 二叉树的层次遍历

c++解答

4.6.5  二叉树的锯齿形层序遍历

给定一颗二叉树,按照锯齿形顺序访问其节点。即相邻层之间的访问顺序相反。

使用队列,按照先左后右的顺序放入子节点。使用一个bool变量标记本层的访问顺序,如果倒序就插入到已有序列的前面,否则放到后面。每层访问完bool变量取反即可。

Leetcode  (medium)  103. 二叉树的锯齿形层次遍历

c++解答

4.6.6  二叉树的最大深度

给定一颗二叉树,求其最大深度。

最大深度即叶节点所在的层数的最大值。层序遍历,记录当前层的顺序,若下一层没有节点时退出。返回层数即可。

Leetcode  (easy)  104. 二叉树的最大深度

c++解答

4.6.7  二叉树的层次遍历II

给定一棵二叉树,打印其层序递减的序列。即每一层从左到右访问,但是按照从下向上的顺序访问每一层。

按照从上到下,从左到右的层序遍历,但是每一层的序列插入到已有序列的前面即可。

Leetcode  (easy)  107. 二叉树的层次遍历 II

c++解答

4.6.8  构造二叉搜索树

给定一个有序序列(无重复值),构造一棵对应的二叉搜索树。

二叉搜索树的逆中序遍历,递归形式。

Leetcode  (easy)  108. 将有序数组转换为二叉搜索树

c++解答

4.6.9  平衡二叉树

给定一棵二叉树,判断其是否为平衡二叉树。

根据平衡二叉树的定义,左右子树的深度之差不能大于1,使用递归形式判断左右子树,因为直到叶节点才能判断深度,所以递归的返回值为当前节点左右子树的最大深度,当左右子树的深度之差大于1时,将全局变量置为false即可。

Leetcode  (easy)  110. 平衡二叉树

c++解答

4.6.10  二叉树的右视图

给定一棵二叉树,返回其右视图序列。即从二叉树的右侧所看到的每一个节点。

层序遍历(从上往下,从左往右),保存每一层的最后一个节点,即每一层的最右侧节点。

Leetcode  (medium)  199. 二叉树的右视图

c++解答

4.6.11 实现Trie(前缀)树

设计一个数据结构,实现单词插入,查询单词,查询前缀三个操作。单词只包含'a' -'z'26个英文字母。

使用Trie树来实现,因为只包含26个小写字母,所以每个节点的子节点有26个,因为需要查询单词,所以节点需要使用一个bool型变量标记是否为某个单词的结尾。

插入:从根节点出发,查询单词对应节点是否存在,若不存在,增加节点,到达单词结尾时,标记当前节点的结尾标记为true。

查询单词:从根节点出发,检查单词对应节点是否存在且其字符值是否等于单词对应字符值,并且当检查到单词最后一个字母时,判断当前节点是否有结尾标记。

查询前缀:同查询单词,但是不需要检查单词尾对应节点的结尾标记。

Leetcode  (medium)  208. 实现 Trie (前缀树)

c++解答

4.6.12  完全二叉树的节点个数

给定一棵完全二叉树,返回其节点个数。完全二叉树只有最下面一层可能有空节点,并且空节点只能在右侧。

层序遍历 统计节点个数

Leetcode  (medium)  222. 完全二叉树的节点个数

c++解答

4.6.13  翻转二叉树

给定一棵二叉树,翻转其每个节点的左右子树。

层序遍历,保存左节点,修改左节点=右节点,修改右节点=保存的左节点。

Leetcode  (easy)  226. 翻转二叉树

c++解答

4.6.14  二叉树的所有路径

给定一棵二叉树,返回从根节点到叶节点的所有路径。

回溯法,注意删除本轮添加的数字字符串和间隔符,将路径放入结果时,删除最后的间隔符。每一轮必须回删至进入时的状态。

Leetcode  (easy)  257. 二叉树的所有路径

c++解答

4.6.15 二叉树的序列化与反序列化

设计两个函数,实现二叉树的数据结构与序列之间相互转换,即TreeNode*与string直接转换。

使用二叉树的层序遍历来构造序列,并对序列进行遍历,按照层序构造二叉树。

数据结构与算法题目及C++解答_第10张图片

Leetcode  (hard)  297. 二叉树的序列化与反序列化

c++解答

4.6.16  二叉树中两节点的最近公共祖先

给定一棵二叉树的根节点和两个节点p,q,求p和q的最近公共祖先。

Leetcode  (medium)  236. 二叉树的最近公共祖先

c++解答  

4.6.17  计算右侧元素小于当前值的个数

给定一个数组,求每个元素右侧小于当前值的个数

如果使用暴力求解,那么O(n*n)肯定会超时。考虑二叉搜索树的特性,大于当前节点的数值都在当前节点右侧,小于等于当前节点的数值都在当前节点左侧。那么根据这个特性,从右向左将数组元素插入到二叉树中,向左移动时,当前节点的左子树节点个数+1,向右移动时,记录当前节点左子树的个数+当前节点个数的数目求和直至nullptr插入当前节点,即为插入数值nums[k]时,其右侧小于该数值的数目。

数据结构与算法题目及C++解答_第11张图片

Leetcode  (hard)  315. 计算右侧小于当前元素的个数

c++解答

4.6.18  二叉树中的最大路径

给定一个二叉树,求从任意节点出发,两端到达叶节点的最大路径。

递归形式解决,最大路径一定有一个根节点,则最大路径=根节点值+左子树最大路径值+右子树最大路径值。dfs每个节点,返回值为其左右子树(包含根节点)的最大路径。在dfs中保存最大的val+left+right即可。

Leetcode  (medium)  124. 二叉树中的最大路径和

c++解答

4.6.19  字典序中的第k小数字

给定数字n和k,求1-n中,字典序第k小的数字。

字典序中第k小的数字即为十叉树中(第一层节点1-9,其后每个节点的子节点均为0-9)中先序遍历第k个数字。使用抽象的十叉树结构,设当前节点为cur,那么右侧节点为cur+1,对于子树,每一层的cur的先序遍历子节点为l-r,l=cur*10^i,r=(cur+1)*10^i,计算子节点个数step,若step>k,说明第k小元素在这个先序遍历中,因此向下一层遍历,更新cur=cur*10,同时由于cur是其子树中最小的节点,因此求所有节点中第k小元素,即为求cur子树中第k-1小元素。同理,若step<=k,说明第k小元素不在这个先序遍历的子树中,因此cur=cur+1,指向字典序同层的下一个节点,同时k-=step,因为cur的子树中所有元素均比cur右侧节点子树中的元素小。

Leetcode  (hard)    440. 字典序的第K小数字    c++解答

5 数值计算&规律

5.1.1 反转整数高低位

高低位转换int型数字时,可能发生int溢出,因此至少要使用比int更大的类型来保存计算结果。

Leetcode  (easy)  7. Reverse Integer 

c++解答 

5.1.2 字符串转INT数值(实现atoi())

注意转换的是字符串中首个可读数字串,首字符可以是'+','-',或数字。先处理字符串,获取首个可读数字串,这样字符串就只有三种情况,首字符为正、负和数字,之后直到字符串结束均为数字,只有使用十进制进位加法即可。同样要使用比int更大的类型来保存计算结果。

Leetcode  (medium)  8. String to Integer (atoi) 

c++解答

5.1.3 十进制数字转罗马数字

输入1-3999的整数,输出转换的罗马数字。

Leetcode 12. 整数转罗马数字

c++解答

5.1.4 十进制加法

给定一个数组表示的十进制数字,高位在前低位在后,求其加1后的值。从最后一位(最低位)开始,只有最低位+1,其余位均只加进位,注意进位即可。

Leetcode  (easy)  66. 加一

c++解答

5.1.5  罗马数字转整数

给定一个字符串形式表示的罗马数字,求其十进制的数值。罗马数字的组合形式是大数在前,相邻字符间有组合关系,当前字符代表的数字比前面的大或与前面相等,则求和,否则求差(后面的值减当前值)。用一个哈希表保存每个罗马字符代表的数字,从后往前遍历罗马字符,根据当前字符的值和前一个字符代表的数值大小关系进行求和或求差。

Leetcode  (easy)  13. 罗马数字转整数

c++解答

5.1.6 分糖果

给定一个糖果总数和n个小朋友,第一轮给i号小朋友分i个糖果,第二轮开始i号小朋友分i+n个糖果,直到剩余糖果为0。当剩余糖果不够当前小朋友应给的糖果时,将剩余糖果全部给当前小朋友。模拟分糖果过程,每一轮更新n,更新剩余糖果数和每个小朋友的糖果总数。

Leetcode  (easy)  1104. 分糖果 II  (周赛题目 暂无题库链接)

c++解答

5.1.7  二进制中1的个数

给定一个32位整数,输出其二进制中1的个数。

对于任意一个数字,按位与1判断最低位是否为1。右移一位,使高位作为最低位,这样持续判断即可。注意如果判断负数,不能使用右移>0作为循环结束条件。使用位数,如int型32位,作为循环较好。

Leecode  (easy)  191. 位1的个数

c++解答

5.1.8 10进制(0-9)转26进制(1-26)

给定一个正整数,求其26进制的数值。1-'A',26-'Z',27-'AZ',...

注意进制从1开始,当能整除时,当前位的值为'Z',求剩余值时n/26-1,要减去当前位的26,其他情况,按照正常的进制进位即可。

Leetcode  (easy)  168. Excel表列名称

c++解答

5.1.9  26进制转10进制

给定一个26进制的字符串,求其十进制数值。

从第一位(高位)开始,乘进位,加当前值。

Leetcode  (easy)  171. Excel表列序号

c++解答

5.1.10  最大数

给定一个数组,求数字前后组合能组成的最大的数。

使用自定义cmp的sort函数,先将相邻两个int转string,判断前后组合哪个字符串的数值较大。对整个数组排序,再顺序将数组中的数字转string,放到结果中即可,注意全为0的情况,即结果首位为0,此时只保留一个0。使用自定义的str2int 和int2str,时间复杂度差别不大。

Leetcode  (medium)  179. 最大数

c++解答

5.1.11  反转二进制位

给定一个int整数,反转其二进制位的高低位,并输出反转后的二进制对应的int整数。

从最低位开始遍历待操作数的每一位(n&1),结果进位(res<<1),获取当前位(res|n&1)。

Leetcode  (easy)  190. 颠倒二进制位

c++解答

5.1.12  快乐数

给定一个正整数,判断是否为快乐数。快乐数的定义,使用当前每一位的平方和更新当前数字,若平方和能为1则为快乐数,否则会一直循环。

模拟平方和更新过程,用哈希表记录出现过的数字,若一个数字出现两次,则一定会在这两个数字间死循环,返回false。否则可以到达平方和为1,返回true。

Leetcode  (easy)  202. 快乐数

c++解答

5.1.13  重合矩形的面积

给定两个矩形的左下角和右上角角点,求其不重合部分的面积。

分三种情况,包含,不相交和相交。包含返回大矩形面积,不相交返回两个矩形面积之和,相交用两个矩形面积之和减去重叠部分。重叠部分面积=min(右上角点1x值,右上角点2x值)-max(左下角点1x值,左下角点2x值)*(min(右上角点1y值,右上角点2y值)-max(左下角点1y值,左下角点2y值))。

Leetcode  (medium)  223. 矩形面积

c++解答

5.1.14  各位相加

给定一个非负整数,每次以各位的和更新当前值,直至为个位数。输出这个个位数。

模拟过程。

Leetcode  (easy)  258. 各位相加

c++解答

5.1.15  丑数

给定一个int整数,判断是否为丑数。

丑数只有质因数2,3,5,且1为丑数。因为乘除法没有优先级。所以能除以2能除以3时无论先除以哪个,不会影响判断。因此每次都判断是否能整除2,3,5,若能,除以其中一个,继续过程。否则不是丑数。

Leetcode  (easy)  263. 丑数

c++解答

5.1.16  今年的第几天

给定一个年月日,求是当年的第几天。

闰年:能整除100且能整除400,或者不能整除100能整除4的年份为闰年。

牛客网  2019校招真题  今年的第几天 

c++解答

5.1.17  数字序列第n位的值

给定一个无限长的序列,从数字1开始,数字k出现了k次,求数列第n位的值。

模拟过程,遍历当前数字的出现当前次数,遍历完当前数字后,当前数字+1,记录总的次数,当总的次数=n时,输出当前数字即可。

牛客网  2019校招真题  数字序列第n位的值 

c++解答

5.1.18  灯泡开关

给定n个灯泡,第一轮全开,第2轮每两个关闭一个,第i轮开始每i个转换状态,求第b轮后有多少灯是亮的。

先使用常规做法找规律,4,5,6全为2,    9,10全为3,所以return sqrt(n)向下取整即可。

Leetcode  (medium)  319. 灯泡开关

c++解答

5.1.19  3的幂

给定一个整数,判断其是否为3的幂。

1.循环除以3,直到不能整除3(不是3的幂),或者到1(是3的幂)2.3的质因数只有3,因此3的低次幂必能被3的高次幂整除,找到32位int范围内最大的3的幂k,判断k%n==0?即可。

Leetcode  (easy)  326. 3的幂

c++解答

5.1.20  质数因子

给定一个long型整数,输出其从小到大的质数因子。

首先若该数为质数,则其质数因子为它自身,否则从2开始依次找能整除的质数,作为当前质数因子,该数除以当前质数因子,继续上述过程,直到该数为1。本题的关键在于如何快速判断一个数字是否为质数,有定理,质数必为6x+1或6x+5(即6x-1),从而在2-num构建质数表时,能省去大量时间。

牛客网 华为机试题  质数因子

c++解答

5.1.21  两数相除

求两个int型数字的除法,要求不使用乘除和取余。

除法的基本运算是减法,但是如果每次都从除数的绝对值中减去被除数,那么时间复杂度太大。因为不能使用乘法,所以使用移位来获取较大的数字,使用2的幂的多项式来逼近除数。即除数=被除数*(2^k1+2^k2+..+1),因为是整形除法,所以最后小于被除数的部分可以去掉,因此可以取等号。所以从较大数开始,i=31,但是要注意1的默认类型为int,1<<31为INT_MIN,所以需要指定1l即long型的1。因为减去当前部分之后的除数,2的幂的系数一定比当前小,所以使用一个循环即可。

Leetcode  (medium)  29. 两数相除

c++解答

5.1.22  被3整除

给定一个数列,其中第i个数代表从1到i的所有数字的排列(将数字放到之前的数字后面组成新数字),如1,2,3...,12345678910,12345678911,...求数列的第l个数到第r个数中,能被3整除的数字。

数据结构与算法题目及C++解答_第12张图片

首先不能遍历对每个数字求是否能被3整除,考虑数列中的数字被3整除是否有规律。可以看出,f(i)的数值每3个一轮进行增加,而每一轮的起始和上一轮的结束值通过,如果模拟这个过程仍然很耗时,再看i-f(i),可以看出每3个数值相同,那么除数一定为3,又每3个数值增加一次,所以每个数字+2,这样三个一轮中,只有第一个会数值增加。因此i-f(i)=(i+2)/3,所以f(i)=i-(i+2)/3,注意不要通分,因为这里是整形加法,向下取整。

牛客网  2019校招真题    被3整除    c++解答

5.1.23  数对

给定一个整数n和整数k,求1-n中所有数字a和b两两组成数对,如果a取余b>=k满足要求,求所有满足要求的数对个数。

n的大小为1-10^5,因此如果遍历求取则时间复杂度O(n*n)会超时。因此考虑规律。首先对于i,j=[1,n],ij的情况,那么如果j<=k,则i%j一定小于k,否则还可以继续取余。因此j从k+1开始,而从j到最后一个数n,其余数循环出现,当前j=k+1,则余数为 1,2,...,j-1,0循环出现,从j开始之后的数字除以j组成数对,共有n-j个数对,而每个循环有j个数字,其中有j-k个数字满足i%j>=k,所以循环中满足要求的数对个数为(n-j)/j*(j-k),若n-j不能整除j,则还有剩余数字不足一个循环,其中余数大于等于k的个数为(n-j)%j-k+1个。

牛客网  2019校招真题    数对    c++解答

6.滑动窗口

对于一维数据,遍历每次访问一个元素。如果需要求其子集的性质,可以使用一个大小为k的滑动窗口来实现。如果子集大小不定,那么滑动窗口的大小可变。通常用于解决子集的连续性问题。

6.1.1 字符串无重复的最大子串长度

使用一个滑动窗口,保存当前不重复的子串。遍历字符串,判断当前字符是否与滑动窗口中的字符串重复,若重复,将滑动窗口中该字符所在位置之前的所有字符全部删除,再添加该字符到滑动窗口中。保存滑动窗口的最大长度即为要求的结果

Leetcode  (medium)  3. 无重复字符的最长子串    c++解答

6.1.2  最小覆盖子串

给定一个字符串s,一个字符串t,求s中包含t中所有字母的最小子串。

窗口中使用哈希表记录t中出现的字母和出现次数,r右移直到当前窗口满足要求,其后l右移缩小当前窗口,直到窗口中的字母出现次数不满足t的要求,每次满足要求的窗口,记录窗口长度和起始位置并更新最小窗口。

Leetcode  (hard)  76. 最小覆盖子串    c++解答

7. 双指针

双指针狭义是指用两个指针,分别对应不同的含义,遍历数组并移动指针,判断是否满足条件。如对于排序的数组,一个指针从前往后遍历,一个指针从后往前遍历,这样可以判断某个和是否可由数组中两个数字组成。

广义的双指针是指两个或两个以上的指针,分别记录不同的含义,当符合不同条件时执行不同操作,从而得到要求的结果。如快慢指针(一个每次走一步,一个k次走一步),正常指针和特殊指针(一个每次走一步,一个符合条件再走)等。

7.1.1 盛水最多的容器

面积=(right-left)*min(arr[j],arr[i]),暴力法会超时。使用双指针方法,双指针分别放在左右两边,根据左右两边的高度,将较矮的一边向较高的一边移动。因为边界高度对容积的影响要远大于每次移动造成的底边长度减1。

Leetcode  (medium)  11. 盛最多水的容器

c++解答

7.1.2 反转字符串

O(1)的空间复杂度,意味着使用的空间大小与输入数据无关。使用双指针,交换首尾字符。直到两个指针相遇。

Leetcode  (easy)  344. 反转字符串

c++解答

7.1.3 回文数

判断一个数字是否是回文数。可以将数字转字符串判断每一位的对称,或者判断每一位上十进制数字的对称。

Leetcode  (easy)  9. 回文数

c++解答

7.1.4 移除数组重复元素

给定一个数组和目标值,将数组中所有等于目标值的数字移动到数组尾,并返回不为目标值的个数。O(1)的空间复杂度,使用两个指针left和right分别从数组头和尾出发,使用一个计数cnt来统计不为val的个数。根据要求,数组中等于val的值要放在数组末尾,而不等于val的值要放到数组前面。所以会有四种情况:左等于右不等于(此时左右交换,left++,right--,cnt++);左等于右等于(左不符,left不动,右符合,right--);左不等于右等于(左符合,右不符合,left++,right不动,cnt++);左不等于右不等于(左符合,left不动,右不符合,right不动,cnt++)。综上,若左右指针中一个不为val,cnt++;左为val,左不动除非交换;右为不为val,右不动,除非交换。总的时间复杂度O(n)。

Leetcode  (medium)  27. 移除元素

c++解答

7.1.5 三数之和

给定一个无序数组,数字可能有重复,任意选出三个数字,求使其和为0的不重复组合。首先可以确定思路是将数组任意一个数字nums[i]作为基准,去剩余数字找两个数,使其和为-nums[i],为防止对i遍历时重复,那么需要对输入数组排序,然后对每个i,去[i+1,size-1]中找两数之和,sort快排的时间复杂度为O(nlogn)。考虑如果构建两数之和,对于排序数组,使用左右指针标记首尾,根据两个指针之和来移动指针,时间复杂度为O(n),所以使用双指针方法总的时间复杂度为O(n*n)。需要注意跳过值相同的i不能使组合完全互异,考虑-5 -5 1 1 4 4,第二个-5的组合不会出现,但是对于第一个-5 其后的 1 1 4 4 会出现两种组合,而这两种组合重复。所以对于每个i进行构建两数之和为-nums[i]时,要检查左右指针的值,若与上个指针的值相同,则移动指针,并跳过本轮判断。

Leetcode (medium)  15. 三数之和 

c++解答

7.1.6 四数之和

三数之和的进阶问题,转化为对每一个nums[i],在其后数组中找三个数使其和为target-nums[i],要对上面的三数之和做一定修改,增加起始索引参数和目标值参数,防止三数之和的遍历使用上面的i造成重复。

Leetcode (medium)  18. 四数之和

c++解答

7.1.7 最接近的三数之和

三数之和问题的变体,若数组中不存在三数之和等于目标值,返回最接近目标值的三数之和。在判断每个nums[i]对应的两数之和中,若两数之和等于两数之和的目标值,返回三数之和目标值并退出,否则判断当前两数之和与目标两数之和的差值的绝对值,并更新最小绝对值差值和最接近三数之和目标值的三数之和。

Leetcode (medium)  16. 最接近的三数之和

c++解答

7.1.8  删除排序数组中的重复项

给定一个排序数组,数组中的数字可能有重复,修改数组 ,令每个重复数字的最大出现次数为2,并且k个符合要求的数字在数组的前k位。要求使用O(1)的空间复杂度。

因为O(1)的空间复杂度,那么只能对数组进行操作,不能遍历数组储存每个数字出现次数。考虑双指针,使用一个指针index作为修改后的数组的索引,一个指针i为遍历当前数组的索引,用一个变量cnt记录当前数字出现的次数。若当前数字不与前一个数字相同,则将当前数字nums[i]放到正确的位置nums[index],index向后,i向后,cnt=1记录当前数字出现次数。若当前数字与前一个数字相同,判断cnt与2的大小,如果cnt<2,那么说明当前数字可以放到index位置,即nums[index]=nums[i],同时 更新cnt=2,index向后,i向后;否则i向后,index不更新,cnt不更新。

Leetcode  (medium)  80. 删除排序数组中的重复项 II

c++解答

7.1.9  验证回文串

给定一个字符串,验证其中的字母和数字是否能组成回文串,其中相同字母的大小写也算是相同字母。

使用双指针法,因为只验证字母和数字,因此跳过非字母和数字的字符,使用一个函数来判断字符是否合法,使用一个函数来判断字母字符是否合法。注意在跳过非法字符时,要保证l

Leetcode  (easy)  125. 验证回文串

c++解答

7.1.10 奖金分配

给定一个正整数无序数列,将其划分为前中后3个连续部分,要求前和后两部分的和相等,求前部分的最大和。

l从开始向后移动,r从结尾向前移动。记录前部分的和suml和后部分的和sumr,若suml

牛客网 2019校招真题  获得最多的奖金

c++解答

7.1.11  第n个丑数

求第n个丑数,1为第一个丑数。

因为质因子为2,3,5,因此从第一个丑数开始,乘以每个质因子,取其中最小的值作为当前的丑数。但是这样1还没有乘完,因为确定第二个丑数为2后,第三个丑数应该为1*3,而不是2*2,同理对于3和5也有这种情况,因此使用一个数组代表当前应该乘的数,即指向丑数数组中的一个数。idx[0]为2应该乘的丑数数组索引,idx[1]为3应该乘的丑数数组索引,idx[2]为5应该乘的丑数数组索引,在确定第二个丑数为2后,idx[0]++指向下一个丑数,这样在确定第三个丑数时,idx[0]=1,idx[1]=0,idx[2]=0;所以第三个丑数为1*3即ugly[idx[1]]*3;因为可能出现三个数中有相同的数字,而丑数不能重复,所以三个丑数中,与当前丑数(最小的那个)相等的,其在丑数数组中的索引都要+1,指向下一个丑数,防止重复。

Leetcode  (medium)  264. 丑数 II

c++解答

7.1.12  超级丑数

给定一个质因数序列primes,每个超级丑数的质因数都在质因数序列中。求第n个超级丑数。

多指针。使用idx[i]代表与primes[i]相乘构造超级丑数的超级丑数ugly[indx[i]]在超级丑数序列中的索引。每次取m个(primes的大小)乘积中最小的那个作为当前的超级丑数,构造超级丑数时,乘积为该值的所有idx++指向超级丑数序列中的下一个,避免重复。

Leetcode  (medium)  313. 超级丑数

c++解答

7.1.13  移动数组元素

给定一个数组,将数组中的0移动到数组后面,保持非0元素的相对顺序。

方法1:迭代器遍历,删除0并记录0的个数k,遍历后在数组后面添加k个0即可。时间复杂度O(k*logn+k),因为每次删除数组元素都需要logN。方法2:双指针,一个指针idx指代移动后数组的非零元素的当前位置,只有将非零元素放在该位置时,该指针才后移,另外一个指针i遍历数组,将非0元素放到数组的idx位置,因为idx必定比i小,所以不会将待移动的元素给覆盖掉。当i遍历完数组后,需要将idx之后的所有元素置为0。时间复杂度O(n+k)。

Leetcode  (easy)  283. 移动零

c++解答

7.1.14  寻找重复数

给定一个大小为n+1的数组,其中有1-n的数字,有一个数字重复了,找出这个重复的数字。要求时间复杂度

数组中重复的数相当于链表有环,因此找重复的数字就转化为了有环链表的入口。利用快慢指针,第一轮每次快指针走两步,慢指针走一步。第一次相等后,慢指针从起点0出发,每次快慢指针都走1步,直至两个指针相等,即为重复的数字。

Leetcode  (medium)  287. 寻找重复数

c++解答

8. 二分查找

如果输入数据满足某种属性, 使得每次都可以将输入数据分为两部分,并且要查找的值只能属于其中一部分。那么就可以使用二分的思想,每次都将数据分为两部分,判断应该继续处理哪一部分。分为具体属性和最优属性搜索,对于具体属性,如搜索一个具体的数字,对于最优属性,如搜索满足条件的最大的或最小的数字。二分的时间复杂度为O(logN),对于大规模的数据,效率非常高。

二分法有4个参数影响搜索过程:1.搜索边界 l和r  2.while循环结束条件(决定最后一次l==r时是否判断)  3.l和r每次移动的距离(在mid基础上左移或右移一步,或mid)4.返回值

对于具体属性的二分查找,l和r为搜索范围,while(l

对于最优属性的二分查找,要根据具体的条件,确定这四个参数。while中可以去l

8.1.1 在排序数组中查找元素的第一个和最后一个位置

给定一个排序数组和目标值,寻找目标值在数组中的起始和结束索引。要求时间复杂度O(logN),因此需要使用二分查找。先使用二分查找,找到目标值在数组中的任意一个位置,再以此位置为起点,向左扩展找最小索引,向右扩展找最大索引。因为重复数字的长度有限,因此时间复杂度只有二分法的O(logN)。

Leetcode  (medium)  34. 在排序数组中查找元素的第一个和最后一个位置

c++解答

8.1.2 搜索插入位置

给定一个排序数组及一个目标值,数组元素互异,若目标值存在,返回其索引,否则将其按顺序插入数组中,并返回索引。因为数组有序,使用二分查找,若未找到,最后左指针l的位置即为要插入的位置,使用.emplace(.begin()+l,target)即可。

Leetcode  (easy)  35. 搜索插入位置 

c++解答

8.1.3 搜索旋转排序数组

给定一个有序数组nums,将其在某个位置旋转后,查找某个数字target是否在旋转后的数组中,要求时间复杂度O(logN)。如有序数组 123456 旋转数组345612 。

时间复杂度O(logN),所以是二分查找, 观察旋转后的数组。如果将数组 从中间分开,那么一半有序,一半无序。有序部分可以确定是否含有要查找的数字target,无序数组不能立即判断,需要再将无序数组分为两部分,同样的,无序数组的两部分也是一半有序一半无序。这样重复下去即可,因为每次都是将数组分为两部分,所以满足二分查找的时间复杂度O(longN)。

Leetcode  (medium)  33. 搜索旋转排序数组

c++解答

8.1.4  求int的平方根

给定一个非负整数x,求其平方根向下取整的整形值。 令y=sqrt(x),则y*y=x(y=x/y),使用二分查找y,如果乘积(或商)小于x,则左指针向右=mid+1,继续二分, 否则右指针向左=mid-1,继续二分。注意循环退出条件(l<=r), 当l=r时,乘积会小于x,此时l增大,l=r+1,乘积会大于x,即若循环中未返回,则应返回r。具体可以通过一个例子来推导,如令x=6。

Leetcode  (easy)  69. x 的平方根

c++解答

8.1.5 搜索旋转排序数组II

给定一个由升序数组(可能有重复元素)旋转一次得到的数组,求某个值是否在数组中。算法的时间复杂度应为log(N),(这句我自己加的,因为题目I要求log(n),不要求log(n)简直是送命题)。

和搜索旋转排序数组I思路类似,每次都用中间值将数组分为两部分,有序和无序,并且每次都能判断目标值是否在有序的那部分。但是由于数组中有重复元素,而重复元素会破坏中间值判断哪部分有序的正确性,因此在求mid之前,l和r都跳过重复元素,直到重复元素的最后一个(l的最后面那个,r的最前面那个)。

Leetcode  (medium)  81. 搜索旋转排序数组 II

c++解答

8.1.6  寻找峰值

给定一个不重复数字组成的数组,求其任意一个峰值的索引。有nums[-1]=nums[size]=-∞。要求时间复杂度O(logN)。

时间复杂度要求O(logN),那么使用二分查找。因为有条件nums[-1]=nums[size]=-∞,所以假设当前搜索到mid,只要mid-1和mid+1有一个的值大于mid的值,向这个方向搜索一定能搜索到一个极值。否则mid就是一个极值。

Leetcode  (medium)  162. 寻找峰值

c++解答

8.1.7  吃香蕉的速度

给定一个数组,每个数值代表香蕉堆里的香蕉个数。要求在k小时内吃完,求每小时吃的最小速度。如果吃的数目小于当前香蕉堆中的香蕉,则吃掉该数目,否则吃掉所有剩下的香蕉,不再吃其他堆,直至下个小时。

二分法,吃的最小速度为1,最大速度为数组中最大香蕉堆的数目,因为求最小速度,而只要>=max(nums),吃的速度就是一样的,即数组的个数。l=1,r=max(nums),mid为当前吃香蕉的速度,求吃完全部香蕉的时间t,若t<=k,则r=mid,否则l=mid+1。因为直接r=mid-1的话,可能搜索过头,而t==k又不能直接返回,因为求最小的速度,而每一轮左右节点又至少有一个需要移动。

Leetcode  (medium)  875. 爱吃香蕉的珂珂

c++解答

8.1.8  H指数

给定一个数组,每个数字代表该论文的被引次数。求最大的被引指数h,h满足在n篇论文中至少有h篇被引次数不小于h。

l=0,因此0对于任何数组都符合;r=nums.size(),因为h的定义为篇数,而最多有n篇论文。对于每个mid,统计n篇论文中,被引次数nums[i]>=mid的总篇数h,若h>=mid,说明当前mid符合题意,需要找最大的mid;否则mid太大,需要减小mid。

Leetcode  (medium)  274. H指数

c++解答

8.1.9  有序矩阵中第k小元素

给定一个二维矩阵,矩阵行列有序,返回矩阵中第k小的元素。

矩阵行列有序,因此m[0][0]为最小元素,m[m-1][n-1]为最大元素,以mid为界统计数组中小于该值的数目并移动l和r。=mid保证mid在矩阵中。

Leetcode  (medium)  378. 有序矩阵中第K小的元素

c++解答

9.后记

你可能感兴趣的:(数据结构与算法)