Live2D

【剑指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. 任何大于1的数都可由2和3相加组成(根据奇偶证明)
  2. 因为2*2=1*4,2*3>1*5, 所以将数字拆成2和3,能得到的积最大
  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;
        }
    }
};

posted @ 2021-03-12 20:00  WSquareJ  阅读(81)  评论(0编辑  收藏  举报