【剑指offer】刷题记录11~18(持更)
注:点击题目可直接跳转至leetcode相应的题目代码提交处
11. 旋转数组的最小数字
题目
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
思路
直接判断后一个数是否比第一个数小,如果小的话就是最小值;如果遍历整个数组都没有这样的值,那么第一个数就是最小的。
代码
class Solution {
public:
int minArray(vector<int>& numbers) {
if(numbers.size()<=0)
{
return -1;
}
int num=numbers.size();
int i;
for( i=1;i<num;i++)
{
if(numbers[i]<numbers[i-1])
{
return numbers[i];
}
}
if(i==num)
{
return numbers[0];
}
return -1;
}
};
12. 矩阵中的路径
题目
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
思路
这道题非常经典常见,可当作模板记住,很多类似的问题都可以使用同一方法。
这道题使用回溯法:首先这道题并没有规定是左上角开始,所以可以判断每一个点适不适合作为匹配的开始,然后再依次判断她的四周,因为路径中不能有重复的点,所以每一步要标记,如果此条路径不通,就回溯,回溯时记得撤销标记;
代码
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
if(board.empty()||board[0].empty())
return word.empty();
int row=board.size();
int col=board[0].size();
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(isExist(board,word,0,i,j))
return true;
}
}
return false;
}
bool isExist(vector<vector<char>>& board,const string &word,int s,int a,int b)
{
if(s==word.size()) return true;
if(a<0||a>=board.size()||b<0||b>=board[0].size()) return false;
if(word[s]!=board[a][b]) return false;
board[a][b]='*';
if(isExist(board,word,s+1,a-1,b)||isExist(board,word,s+1,a+1,b)||
isExist(board,word,s+1,a,b+1)||isExist(board,word,s+1,a,b-1))
return true;
board[a][b]=word[s];
return false;
}
};
13. 机器人的运动范围
题目
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
思路
这个题和第12题很相似,但是是计算可以走的格子的位置,可以用dfs,遍历可以走的位置,然后每到一个点,判断这个点是否符合规则,如果符合,则可以计入总数,但要注意不能重复计数。因此可以设置一个标记数组,走过的位置不再走,(因为走过的位置下一步dfs永远一样,如果不标记会无限循环);注意:与上题还有个不同点是,这一题的机器人只能从(0,0)开始;
代码
class Solution {
public:
int movingCount(int m, int n, int k) {
if(k==0)
return 1;
vector<vector<int>> flag(m,vector<int>(n,0));
isMoving(flag,0,0,k);
int sum=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
sum+=flag[i][j];
}
}
return sum;
}
void isMoving(vector<vector<int>> &flag,int i,int j,int k)
{
if(i<0||i>=flag.size()||j<0||j>=flag[0].size())
return;
if((i % 10 + i / 10 + j % 10 + j / 10)>k)
return;
if(flag[i][j]==1)
return;
flag[i][j]=1;
isMoving(flag,i,j+1,k);
isMoving(flag,i,j-1,k);
isMoving(flag,i+1,j,k);
isMoving(flag,i-1,j,k);
}
};
我采用的是计算标记的数量来计算总数,其他题解里是边走边计数,速度会更快一些。
14- I. 剪绳子
题目
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]k[1]...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
思路
显然是一个动态规划,和前面的上楼梯是一个类型;
首先,n>=2,m>1表示绳子必须切,不能不减断,例如绳长为2时,输出值为1,而不是2;绳长为3时输出2;由于4开始有多种分法,所以我们以4开始进行动态规划式子讨论。可以对一根绳子从1开始遍历切割,切割的两部分分别取其最大值,然后相乘,如果超过了原来的切割最大值,则进行更新,否则不变;
dp[i]=max(dp[i],dp[i-j]*dp[j]);
对于初始值,dp[1];dp[2];dp[3]的定义十分重要,虽然它们直接返回时为1或2,但是在切割中(切割就证明最少已经有两段了,切下来的1、2、3长的绳子可以不必再分)他们的初值分别为1,2,3;
代码
class Solution {
public:
int cuttingRope(int n) {
vector<int> dp(n+1,0);
if(n==2)
return 1;
if(n==3)
return 2;
dp[1]=1;
dp[2]=2;
dp[3]=3;
for(int i=4;i<=n;i++)
{
for(int j=1;j<=i/2;j++)
{
dp[i]=max(dp[i],dp[i-j]*dp[j]);
}
}
return dp[n];
}
};
14- II. 剪绳子 II
题目
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]k[1]...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
2 <= n <= 1000
思路
这一题与上一题的不同点在于数据更大,当需要取余的时候无法进行大小比较,所以上一题的方法无法使用。
参考了评论区的解法:
数论中的一些结论:
- 任何大于1的数都可由2和3相加组成(根据奇偶证明)
- 因为
2*2=1*4,2*3>1*5,
所以将数字拆成2和3,能得到的积最大 - 因为
2*2*2<3*3,
所以3越多积越大 时间复杂度O(n/3),用幂函数可以达到O(log(n/3)), 因为n不大,所以提升意义不大。 空间复杂度常数复杂度O(1)
代码
class Solution {
public:
int cuttingRope(int n) {
vector<int> dp(n+1,0);
if(n==2)
return 1;
if(n==3)
return 2;
dp[1]=1;
dp[2]=2;
dp[3]=3;
for(int i=4;i<=n;i++)
{
for(int j=1;j<=i/2;j++)
{
dp[i]=max(dp[i],dp[i-j]*dp[j]);
}
}
return dp[n];
}
};
15. 二进制中1的个数
题目
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
思路
这一题很简单
代码
class Solution {
public:
int hammingWeight(uint32_t n) {
int sum=0;
for(int i=0;i<32;i++)
{
sum+=n%2;
n=n>>1;
}
return sum;
}
};
16. 数值的整数次方
题目
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
思路
将n化为二进制(指计算过程中看作二进制),转化成x的平方运算,这样可以避免正负数分开运算,复数即使移位仍然是复数;递归法
代码
class Solution {
public:
double myPow(double x, int n) {
if(n==0)
{
return 1;
}
if(n==-1)
{
return 1/x;
}
if(n&1) return myPow(x*x,n>>1)*x;
else return myPow(x*x,n>>1);
}
};
17. 打印从1到最大的n位数
题目
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
用返回一个整数列表来代替打印
n 为正整数
思路
直接将数据放进去就可
代码
class Solution {
public:
vector<int> printNumbers(int n) {
int num=10;
for(int i=0;i<(n-1);i++)
{
num=num*10;
}
vector<int> a(num-1,0);
for(int i=1;i<num;i++)
{
a[i-1]=i;
}
return a;
}
};
18. 删除链表的节点
题目
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
思路
如果是第一个节点就直接返回head->next
,然后设置两个指针,一个指针遍历链表,一个指针标记正在遍历节点的上一个节点,当遇到需要删除的目标节点时,就使用标记指针和遍历指针删除。
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteNode(ListNode* head, int val) {
ListNode* newhead=head;
ListNode* l=head;
ListNode* n=l->next;
if(l->val==val)
{
newhead=n;
return newhead;
}
while(n->val!=val&&n->next!=NULL){
l=n;
n=n->next;
}
if(n->next==NULL&&n->val!=val)
return newhead;
else if(n->next==NULL&&n->val==val)
{
n=NULL;
l->next=NULL;
return newhead;
}
else
{
n=n->next;
l->next=n;
return newhead;
}
}
};