zoukankan      html  css  js  c++  java
  • 【剑指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;
            }
        }
    };
    

  • 相关阅读:
    HDU 5791 Two (DP)
    POJ 1088 滑雪 (DPor记忆化搜索)
    LightOJ 1011
    POJ 1787 Charlie's Change (多重背包 带结果组成)
    HDU 5550 Game Rooms (ccpc2015 K)(dp)
    HDU 5542 The Battle of Chibi (ccpc 南阳 C)(DP 树状数组 离散化)
    HDU 5543 Pick The Sticks (01背包)
    HDU 5546 Ancient Go (ccpc2015南阳G)
    NB-IoT的DRX、eDRX、PSM三个模式 (转载,描述的简单易懂)
    MQTT 嵌入式端通讯协议解析(转)
  • 原文地址:https://www.cnblogs.com/wwj321/p/14526105.html
Copyright © 2011-2022 走看看