• 剑指offer题解和笔记(一刷)


    目录

    引言

    3.21一刷结束

    问题的关键和证明的必需:循环不变式

    数据结构-数组

    数组中重复的数字

    在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

    这个绝对不是简单题,是与面试官交流的问题

    • 时间优先:字典,哈希表
    • 空间优先:利用题中性质:

    范围在(0...n-1)所以如果没出现重复的数,重排之后是按照0~n-1这样排序的。

    这题简单的方法是直接排序,这样时间复杂度(O(nlogn))

    但是可以更优化一下,如果当前位置(i)不等于(a[i]),就让位置(i)与位置(a[i])的值交换

    这样每个数最多交换两次,第一次交换到(i)位置,第二次交换回到正确位置

    如果(a[i]==a[a[i]]),那么说明找到重复的了

    维护不变式:每次循环前在(i)之前的值是按顺序的

    class Solution {
    public:
        int findRepeatNumber(vector<int>& nums) {
            int ans = -1;
            for(int i = 0;i < nums.size();++i){
                while(i!=nums[i]){//
                    if(nums[nums[i]] == nums[i]) {
                        ans = nums[i];
                        break;
                    }
                    else swap(nums[i],nums[nums[i]]); 
                }
                if(ans != -1) break;
            }
            return ans;
        }
    };
    

    二维数组中的查找

    在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

    第一次我直接用二分

    但是有更简单的做法,如果站在右上角看的话就是一个查找二叉树

    性质:往下就是大的,往左就是小的。

    class Solution {
    public:
        bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
            bool isFound = 0;
            if(matrix.empty()) return 0;
            int m = matrix[0].size(),n = matrix.size();
            int i = 0,j = m - 1;
            while(i < n && j != -1){
                if(matrix[i][j] == target){
                    isFound = 1;
                    break;
                }
                if(matrix[i][j] > target){
                    --j;
                }
                else if(matrix[i][j] < target){
                    ++i;
                }
            }
            return isFound;
        }
    };
    

    这题更重要的是测试样例

    • 二维数组里存在的数
    • 不存在的数
    • 空指针

    替换空格

    请实现一个函数,把字符串 s 中的每个空格替换成"%20"

    如果是要求时间最快写得最简单,完全可以新建字符串

    时间复杂度(O(n)),空间复杂度$O(n) $

    class Solution {
    public:
        string replaceSpace(string s) {
            int len = s.length();
            if(len == 0) return s;
            string ans;
            for(int i = 0;i < len;++i){
                if(s[i]==' '){
                    ans += "%20";
                }
                else ans += s[i];
            }
            return ans;
        }
    };
    

    如果要求空间最小,那么需要在原字符串上进行替换操作。

    • (O(n^2)):这个很容易想到,每出现一个空格,后面就移位
    • (O(n)):采用从后往前的做法,这样每个字符只用移动一次

    先求出空格个数,设定两个指针,一个指向要移动到的位置,一个指向原来的位置

    即:将旧值复制到新的位置

    ps:这题leetcode上差点意思,我就在牛客上提交的

    时间复杂度(O(n)),没有新开空间

    class Solution {
    public:
    	void replaceSpace(char *str,int length) {
            if(length == 0) return;
            int numberOfBlank = 0;
            for(int i = 0;i < length;++i){
                if(str[i] == ' ') numberOfBlank++;
            }
            int newStingLength = length + (numberOfBlank << 1);
            int p1 = newStingLength - 1,p2 = length - 1;
            
            while(p2 >= 0){
                if(str[p2] == ' '){
                    str[p1--] = '0';
                    str[p1--] = '2';
                    str[p1--] = '%';
                }else{
                    str[p1--] = str[p2];
                }
                p2--;
            }
    	}
    };
    

    数据结构-链表

    链表我经常犯的错误是忘记++即p = p -> next

    从头到尾打印链表

    输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

    一开始我写成了不能开辟额外空间的链表反转

    这种情况一定要问清楚,可不可以改变原链表的形状

    时间复杂度(O(n))

    class Solution {
    public:
        vector<int> reversePrint(ListNode* head) {
             vector<int> ans;
            if(head == NULL) return ans;
            ListNode *preNode = NULL,*nowNode = head,*tmpNode = NULL;
            while(nowNode->next){
                tmpNode = nowNode->next;
                nowNode -> next = preNode;
                preNode = nowNode;
                nowNode = tmpNode;
            }
            nowNode -> next = preNode;
            while(nowNode){
                ans.push_back(nowNode->val);
                nowNode = nowNode -> next;
            }
            return ans;
        }
    };
    

    但是这种题已经要求了用数组存,那就可以用更简单更快的方法

    class Solution {
    public:
        vector<int> reversePrint(ListNode* head) {
             vector<int> ans;
            if(head == NULL) return ans;
            ListNode *nowNode = head;
            int listLength = 0;
            while(nowNode){
                ans.push_back(nowNode->val);
                nowNode = nowNode ->next;
                listLength++;
            }
            for(int i = 0;i < listLength/2;++i){
                swap(ans[i],ans[listLength-i-1]);
            }
            return ans;
        }
    };
    

    翻转可以当作以中间的数作为根节点,然后左右子树交换。

    除此之外还可采用栈的方法,用栈的方法可读性更强

    数据结构-树

    重建二叉树

    输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

    前序遍历找根,然后中序遍历根的两侧就是子树

    先确定子树的范围,然后先左子树再右子树地确定根

    class Solution {
    private:
         map<int,int>posTable;//在知道前序遍历值的情况下得到中序遍历的位置
    public:
        TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
            if(preorder.size() == 0) return NULL;
            int n = preorder.size(),k=0;
            for(int i = 0;i < n;++i){
                posTable[inorder[i]] = i;
            }
            return build(preorder,inorder,0,n-1,k);
        }
        //每次递归都能找到根
        TreeNode* build(const vector<int>& preorder,const vector<int>& inorder,int l,int r,int& pos){//前序遍历重建树
            if(l > r) return NULL;
            int p = posTable[preorder[pos++]];//得到根
            TreeNode* node = new TreeNode(inorder[p]);
            node -> left = build(preorder,inorder,l,p-1,pos);//左儿子
            node -> right = build(preorder,inorder,p+1,r,pos);
            return node;
        }
    };
    

    如果要求不能用map,那就只能遍历l到r找根点了

    二叉树的下一个结点

    给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

    是父亲的右结点:那是右儿子的左链最深

    若无右儿子:找到第一个是父亲左儿子的fa

    画个图就明白了

    class Solution {
    public:
        TreeLinkNode* GetNext(TreeLinkNode* pNode){
            if(pNode == nullptr){
                return nullptr;
            }
            if(pNode -> right != nullptr){
                TreeLinkNode* now = pNode -> right;
                while(now -> left != nullptr){
                    now = now -> left;
                }
                return now;
            }
            if(pNode -> next != nullptr){
              TreeLinkNode* now = pNode;
              while(now -> next != nullptr && now -> next -> right == now){
                   now = now -> next;      
              }
              return now -> next;
            }
            return nullptr;
        }
    };
    

    数据结构-栈和队列

    用两个栈实现队列

    用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

    用栈可以实现数组翻转

    可以翻转输出到另外一个栈

    class Solution{
    public:
        void push(int node) {
            stack1.push(node);
        }
    
        int pop() {
            if(!stack2.empty()) {
                int val = stack2.top();
                stack2.pop();
                return val;
            }
            else {
                while(!stack1.empty()){
                    stack2.push(stack1.top());
                    stack1.pop();
                }
                int val = stack2.top();
                stack2.pop();
                return val;
            }
        }
    
    private:
        stack<int> stack1;
        stack<int> stack2;
    };
    

    算法和数据操作-递归和循环

    斐波那契数列

    如果希望代码简介,直接递归即可

    但是那效率太低了

    所以采用循环

    class Solution {
        private:
        const int mod = 1e9+7;
    public:
        int fib(int n) {
            int fibNMinusOne = 1;
            int fibNMinusTwo = 0;
            if(n == 0) return fibNMinusTwo;
            if(n == 1) return fibNMinusOne;
            for(int i = 2;i <= n;++i){
                fibNMinusTwo = (fibNMinusOne + fibNMinusTwo)%mod;
                swap(fibNMinusTwo,fibNMinusOne);
            }
            return fibNMinusOne;
        }
    };
    

    本题还有矩阵快速幂的做法

    一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

    这题是斐波那契的简单运用。

    无非是这个递推式:dp[n]=dp[n-1]+dp[n-2]可以由上一个台阶或上两个台阶的方案数转移过来

    算法和数据操作-排序和查找

    旋转数组的最小数字

    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

    一个比较特别的二分,就是跟以前在单调递增的二分不一样

    二分边界条件怎么确定?要明白一点:(l+r)>>1取到的是l

    如果答案是3,此时l为2,r为3,那么答案就是mid+1

    如果l为3,r为4,那么答案就是mid

    其中有重复的,那就要去重

    class Solution {
    public:
        int minArray(vector<int>& numbers) {
            int l=0,r = numbers.size()-1;
            while(l < r){
               int mid = (l+r)>>1;
               if(numbers[mid]>numbers[r]){
                   l = mid+1;
               }else if(numbers[mid] < numbers[r]){
                   r = mid;
               }else{
                   r--;
               }
            }
            return numbers[l];
        }
    };
    

    算法和数据操作-回溯法

    矩阵中的路径

    请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

    [["a","b","c","e"],
    ["s","f","c","s"],
    ["a","d","e","e"]]

    但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

    要注意这题不能用广搜,因为是求一整条路径

    class Solution {
        private:
        vector<vector<bool> >isVisit;
        int rows,cols;
    public:
        bool exist(vector<vector<char>>& board, string word){
            int dx[]={0,0,1,-1};
            int dy[]={1,-1,0,0};
            rows = board.size();
            cols = board[0].size();
            for(int i=0;i<rows;++i){
                isVisit.push_back(vector<bool>());
                for(int j=0;j<cols;++j){
                    isVisit[i].push_back(0);
                }
            }
            bool isFound = 0;
            for(int i = 0;i < rows;++i){
                for(int j = 0;j < cols;++j){
                    if(board[i][j] == word[0]){
                        isVisit[i][j] = 1;//board[i][j]=' '
                        isFound |= dfs(i,j,1,word,board,dx,dy);
                        isVisit[i][j] = 0;
                    }
                }
            }
            return isFound;
        }
        inline bool check(int row,int col){
            if(row >= 0 && row < rows && col >= 0 && col < cols){
                 if(isVisit[row][col] == 0){
                     return true;
                 }
                 return false;
            }
            return 0;
        }
        bool dfs(int row,int col,int pos,const string &word,const vector<vector<char>>& board,int dx[],int dy[]){
            bool isFound = 0;
            if(pos == word.size()) return true;
            for(int i=0;i<4;++i){
                if(check(row+dx[i],col+dy[i])){
                        if(board[row + dx[i]][col + dy[i]] == word[pos]){
                        isVisit[row + dx[i]][col + dy[i]] = 1;
                        isFound = dfs(row + dx[i],col + dy[i],pos + 1,word,board,dx,dy);
                        isVisit[row + dx[i]][col + dy[i]] = 0;
                        if(isFound) break;
                    }
                }
            }
            if(isFound) return true;
            return false;
        }
    };
    

    可以加一个优化,其实vis是没有必要的,可以直接修改board

    机器人的运动范围

    地上有一个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。请问该机器人能够到达多少个格子?

    dfs搜一遍就行,就是搜索求连通块

    class Solution {
    private:
        int dx[4]={0,0,-1,1};
        int dy[4]={1,-1,0,0};
    public:
        int movingCount(int m, int n, int k) {
            vector<vector<bool> >isVisit(m,vector<bool>(n,0));
            return dfs(0,0,m,n,k,isVisit);
        }
        inline bool check(int row,int col,int &m,int &n,int &k, vector<vector<bool> >&isVisit){
            if(row >= 0 && row < m && col >= 0 && col < n){
                if(isVisit[row][col] == 0){
                    if(getNSum(row)+getNSum(col) <= k) return true;
                }
                return false;
            }
            return false;
        }
        inline int getNSum(int num){
            int res = 0;
            while(num){
                res += num%10;
                num/=10;
            }
            return res;
        }
        int dfs(int row,int col,int &m,int &n,int &k, vector<vector<bool> >&isVisit){
            int sum = 1;
            isVisit[row][col] = 1;
            for(int i = 0;i < 4;++i){
                if(check(row + dx[i],col + dy[i],m,n,k,isVisit)){
                    sum += dfs(row + dx[i],col + dy[i],m,n,k,isVisit);
                }
            }
            return sum;
        }
    };
    

    算法和数据操作-动态规划和贪心策略

    剪绳子

    给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0] * k[1] * ... * k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

    • 动态规划做法

    f[n] = max(f[n-i]*f[i])

    但是要特判一下前几个,1,2,3都可以不减

    class Solution {
    
    public:
        int cuttingRope(int n) {
            if(n < 2) return 0;
            if(n == 2) return 1;
            if(n == 3) return 2;
            int *dp = new int[n+1];
            dp[1] = 1;
            dp[2] = 2;
            dp[3] = 3;
            for(int i=4;i<=n;++i){
                dp[i] = 0;
                for(int j = 2;j <= i/2;++j){
                    dp[i] = max(dp[i],dp[j]*dp[i-j]);
                }
            }
            int maxx = dp[n];
            delete []dp;
            return maxx;
        }
    };
    
    • 贪心做法

    优先取3,否则取2

    证明:3(n-3)>n 2(n-2)>n 3(n-3)>2(n-2)(ngeq 5) 成立

    同时:(3(n-3)<4(n-4))(n>7)的时候成立,但其实是f(4)*f(3)

    如果绳长为4,那应该采用取2的做法;

    class Solution {
    
    public:
        int cuttingRope(int n) {
            if(n < 2) return 0;
            if(n == 2) return 1;
            if(n == 3) return 2;
            
            int timesOf3 = n/3;
            if(n - timesOf3*3 == 1){
                timesOf3--;
            }
            int timesOf2 = (n - timesOf3*3) >> 1;
            return (int)pow(3,timesOf3)*(1 << timesOf2);
        }
    };
    

    算法和数据操作-位运算

    二进制中1的个数

    n&(-n)代表的是最后一个1的位置 相当于lowbit运算

    class Solution {
    public:
        int hammingWeight(uint32_t n) {
            int ans = 0;
            while(n){
                ans++;
                n -= n&(-n);
            }
            return ans;
        }
    };
    

    高质量的代码-代码的完整性

    数值的整数平方

    实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

    要注意数据范围,n可能为负数,然后这题要用快速幂

    我一开始当n为负数的时候取反然后再乘,这个想法没问题

    但是,如果(n=-2^{31})会怎么样?

    取反会溢出,炸Int

    不管n是正数还是负数,都可以用快速幂,因为奇偶不分正负。

    就是不能再用n>>=1

    class Solution {
    public:
        double myPow(double x, int n) {
            double ans = 1.0;
            if(n == 0) ans = 1.0;
            else if(n > 0){
               ans = qpow(x,n);
            }else{
              // n = n+1;
               ans = qpow(x,n);
               ans = 1.0/ans;
            }
            return ans;
        }
        inline double qpow(double x,int n){
            double res = 1.0;
            while(n){
                if(n&1) res = res * x;
                x = x * x;
                n/=2;
            }
            return res;
        }
    };
    

    打印从1到最大的n位数

    输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

    (n)可能很大的话,就采用大数加法的写法。

    有一个注意的地方,就是判断是否到最大值那里,如果一直用strcmp来与最大值进行比较,每次比较的复杂度都是(O(n))的,这样不可取。

    有两种方法优化:

    • 如果(i=0)的时候还有进位,那就到达最大值了
    • 可以算出总共有多少个数,然后for就行
    class Solution {
    public:
        vector<int> printNumbers(int n) {
            char *number = new char[n+1];
            vector<int> ans;
            memset(number,'0',n);
            number[n] = '';
            while(!checkNumber(number,n)){
                ans.push_back(changeNumber(number,n));
            }
            return ans;
        }
        int changeNumber(char *number,int n){
            int res = 0;
            for(int i = 0;i < n;++i){
                res = res*10 + number[i]-'0';
            }
            return res;
        }
        bool checkNumber(char *number,int n){
            int nLength = n;
            bool isOverFlow = 0;
            int isTakeOver=1;
            for(int i = nLength - 1;i >= 0;--i){
                number[i] = number[i] + isTakeOver;
                if(number[i] > '9'){
                    if(i == 0) {
                        isOverFlow = 1;
                        break;
                    }
                    number[i] = '0';
                    isTakeOver = 1;
                }else{
                    isTakeOver = 0;
                }
            }
            return isOverFlow;
        }
    };
    

    还有一种全排列的写法:

    class Solution {
    
    public:
        vector<int> printNumbers(int n) {
            char *str = new char[n+1];
            vector<int>ans;
            dfsChooseNum(0,n,str,ans);
            return ans;
        }
        void dfsChooseNum(int pos,const int& n,char* now,vector<int>& ans){
            if(pos == n){
                int res = 0;
                for(int i = 0;i < n;++i){
                    res = res*10 + now[i] - '0';
                }
                if(res) ans.push_back(res);
                return;
            }
            for(int i=0;i<=9;++i){
                now[pos] = '0' + i;
                dfsChooseNum(pos+1,n,now,ans);
            }
        }
    };
    

    删除链表的结点

    链表模拟题

    class Solution {
    public:
        ListNode* deleteNode(ListNode* head, int val) {
            ListNode* toDeleteNode = NULL,*preNode = NULL;
            for(ListNode* now = head;now != NULL;now = now -> next){
                if(now->val == val){
                    toDeleteNode = now;
                    break;
                }
                preNode = now;
            }
            if(preNode != NULL){
                preNode -> next = toDeleteNode -> next;
                 delete toDeleteNode;
            }
            else {
                head = toDeleteNode -> next;
            }
            return head;
        }
    };
    

    正则表达式匹配

    请实现一个函数用来匹配包含'. '和 '*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。

    这是一个有限自动机

    • 当前状态匹配:
      • 判断下一个是不是'*',如果是
        • 可能含义是出现0次,那这次匹配就不算,str不移动,匹配串右移2(*不参与匹配)
        • 可能含义是出现1次,那这次匹配算,str右移1,匹配串右移2
        • 可能含义是出现n次,那这次匹配算,str右移1,匹配串不移动
      • 不是'*',str右移1,匹配串右移1
    • 当前状态不匹配:
      • 判断下一个是不是'*':如果是,就把'*'当做出现0次,这次匹配不算
      • 不是'*',返回false
    • 终止状态:两个串同时为''返回true,如果匹配串为空而str不为空,返回false
    • 注意,并不是str为空就终止,可能匹配串是'a*'这类
    class Solution {
    public:
        bool isMatch(string s, string p) {
            if(s.size() == 0&&p.size() == 0){
                return true;
            }
            char *str = s.data(),*pattern = p.data();
            return matchState(str,pattern);
        }
        bool matchState(char *str,char *pattern){
            if(*str == '' && *pattern == '') return true;
            if(*str != '' && *pattern == '') return false;
    
            if(*pattern == *str ||(*pattern == '.' && *str != '')){
                if(*(pattern + 1) != '*'){
                    return matchState(str + 1,pattern + 1);
                }else{
                    return matchState(str,pattern + 2)||//0
                           matchState(str + 1,pattern + 2)||//next state
                           matchState(str + 1,pattern);//ignore
                }
            }
            
            if(*(pattern + 1) == '*'){
                return matchState(str,pattern + 2);
            }
    
            return false;
        }
    };
    

    动态规划做法

    回溯的做法是向前推导,看当前状态能转移到哪些状态,再回溯

    动态规划的做法就是当前的状态是考虑当前状态是怎么得出的

    样例测试:

    1. aaa a**

    2. aaa a.*

    3. b a*

    class Solution {
    public:
        bool isMatch(string s, string p) {
            int sLength = s.size() , pLength = p.size();
            bool dp[sLength + 1][pLength + 1];
    
            memset(dp,0,sizeof(dp));
            dp[0][0] = true;
            for(int i = 2;i <= pLength;++i){
                if(p[i-1] == '*' && dp[0][i-2] == true){
                    dp[0][i] = true;
                }
            }
    
            for(int i = 1;i <= sLength;++i){
                for(int j = 1;j <= pLength;++j){
                    if(s[i-1] == p[j-1] || p[j - 1] == '.'){
                        dp[i][j] = dp[i-1][j-1];
                    }else if(p[j - 1] == '*'){
                        dp[i][j] = dp[i][j-1]||dp[i][j-2]||(dp[i-1][j] && (s[i-1] == p[j-2] || p[j-2]=='.'));
                    }else dp[i][j] = false;
                }
            }
    
            return dp[sLength][pLength] == 1;
        }
    };
    

    表示数值的字符串

    请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"0123"及"-1E-16"都表示数值,但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是。

    一个很恶心的模拟

    根据数据来敲代码还是不难的

    按照状态的顺序来执行

    class Solution {
    public:
        bool isNumber(string s) {
            char *str = s.data();
            checkSpace(str);
            bool flag = checkNumber(str);
            if(!flag) return false;
            if(*str == 'e'){
                ++str;
                if(*str == '') return false;
                flag = checkInteger(str);
                if(!flag) return false;
            }
           return checkTail(str);
        }
        bool checkNumber(char* &str){
            if(*str == '+' || *str == '-') ++str;
            return checkNum1(str);
        }
        bool checkInteger(char* &str){
            if(*str == '+'||*str == '-') ++str;
            return checkNum2(str);
        }
        bool checkNum1(char* &str){
            bool flag = 0;
            while(isdigit(*str)) ++str,flag = 1;
            if(*str == '.') ++str;
            while(isdigit(*str)) ++str,flag = 1;
            return flag;
        }
         bool checkNum2(char* &str){
            bool flag = 0;
            while(isdigit(*str)) ++str,flag = 1;
            return flag;
        }
        void checkSpace(char* &str){
            while(*str == ' ') ++str;
        }
        bool checkTail(char* &str){
             checkSpace(str);
            if(*str == '') return true;
            return false;
        }
    };
    

    调整数组顺序使奇数位于偶数前面

    输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

    reverse就一定要想到交换

    双指针,两个指针外的都是满足题意的

    class Solution {
    public:
        vector<int> exchange(vector<int>& nums) {
            int lastNums = nums.size() - 1;
            int firstNums = 0;
            while(firstNums < lastNums){
                if(!(nums[lastNums]&1)) lastNums--;
                else if(nums[firstNums]&1) firstNums++;
                else{
                    swap(nums[firstNums],nums[lastNums]);
                    firstNums++;
                    lastNums--;
                }
            }
            return nums;
        }
    };
    

    高质量的代码-代码的鲁棒性

    链表中倒数第k个节点

    快慢指针

    这样就只用遍历一次

    class Solution {
    public:
        ListNode* getKthFromEnd(ListNode* head, int k) {
            ListNode *pFirst = head ,*pSecond = head;
            if(pFirst == NULL) return pFirst;
            short pCount = 1;
            while(pSecond ->next != NULL){
                if(pCount >= k){
                    pFirst = pFirst ->next;
                }
                pSecond = pSecond -> next;
                pCount++;
            }
            if(pCount < k) return NULL;
            return pFirst;
        }
    };
    

    链表中环的入口结点

    快慢指针,如果存在环,就一定会有相遇点

    要注意相遇点并不是入口结点,找到相遇点之后就可以求出环的长度

    然后可以用上题的方法,先让快指针先跑环的长度,再求出相遇的时候,就是入口结点

    class Solution {
    public:
        ListNode* EntryNodeOfLoop(ListNode* pHead){
            ListNode* pMeet = MeetingNode(pHead);
            if(pMeet == nullptr){
                return nullptr;
            }
            int loopNum = 1;
            ListNode* pNode = pMeet;
            while(pNode -> next != pMeet){
                loopNum++;
                pNode = pNode -> next;
            }
            ListNode* pFast = pHead;
            ListNode* pSlow = pHead;
            while(loopNum > 0){
                pFast = pFast -> next;
                loopNum--;
            }
            while(pSlow != pFast){
                pSlow = pSlow -> next;
                pFast = pFast -> next;
            }
            return pSlow;
        }
        ListNode* MeetingNode(ListNode* pHead){
            if(pHead == nullptr || pHead -> next == nullptr){
                return nullptr;
            }
            ListNode* pSlow = pHead -> next;
            ListNode* pFast = pSlow -> next;
            
            while(pSlow != nullptr && pFast != nullptr){
                if(pSlow == pFast){
                    return pSlow;
                }
                 pSlow = pSlow -> next; 
                 pFast = pFast -> next;
                 if(pFast != nullptr){
                     pFast = pFast -> next;
                 }
            }
            return nullptr;
        }
    };
    

    反转链表

    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            ListNode *pre = NULL,*now = head,*tmp = NULL;
            if(now == NULL) return now;
            while(now -> next != NULL){
                tmp = now -> next;
                now -> next = pre;
                pre = now;
                now = tmp;
            }
            now -> next = pre;
            return now;
        }
    };
    

    合并两个排序的链表

    类似于归并排序中的合并,加了个头结点让编程更舒服

    如果不能用的话,那就要记录一下第一个节点

    class Solution {
    public:
        ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
            ListNode* head = new ListNode(0);
            ListNode* lastNode = head;
            while(l1 != NULL && l2 != NULL){
                if(l1 -> val <= l2 -> val){
                    lastNode -> next = l1;
                    l1 = l1 -> next;
                }else{
                    lastNode -> next = l2;
                    l2 = l2 -> next;
                }
                lastNode = lastNode -> next;
            }
            if(l1 != NULL){
                lastNode -> next = l1;
            }
            if(l2 !=NULL){
                lastNode -> next = l2;
            }
            return head -> next;
        }
    };
    

    树的子结构

    前序遍历暴力判断

    class Solution {
    public:
        bool isSubStructure(TreeNode* A, TreeNode* B) {
            if(A == NULL || B == NULL) return false;
            bool flag = 0;
            preOrder(A,B,flag);
            return flag;
        }
        void preOrder(TreeNode *a,TreeNode *b,bool& flag){
            if(a -> val == b -> val){
                flag |= check(a,b);
            }
            if(a -> left)  preOrder(a -> left,b,flag);
            if(a -> right) preOrder(a -> right,b,flag);
        }
        bool check(TreeNode *a,TreeNode *b){
            if(a -> val != b -> val) return false;
            if(b->right != NULL){
                if(a -> right == NULL) return false;
                return check(a -> right,b -> right);
            }
            if(b->left != NULL){
                if(a -> left == NULL) return false;
                return check(a -> left,b -> left);
            }
            return true;
        }
    };
    

    后来我想了一下,我觉得后序遍历应该更快一定

    继承了子树的答案,如果正确那就直接更新

    class Solution {
    public:
        bool isSubStructure(TreeNode* A, TreeNode* B) {
            if(A == NULL || B == NULL) return false;
            return behindOrder(A,B);
        }
        bool behindOrder(TreeNode *a,TreeNode *b){
            if(a -> left)   if(behindOrder(a -> left,b)) return true;
            if(a -> right)  if(behindOrder(a -> right,b)) return true;
            if(a -> val == b -> val){
                if(check(a,b)) return true;
            }
            return false;
        }
        bool check(TreeNode *a,TreeNode *b){
            if(a -> val != b -> val) return false;
            if(b->right != NULL){
                if(a -> right == NULL) return false;
                return check(a -> right,b -> right);
            }
            if(b->left != NULL){
                if(a -> left == NULL) return false;
                return check(a -> left,b -> left);
            }
            return true;
        }
    };
    

    解决面试题的思路-画图让抽象问题形象化

    二叉树的镜像

    请完成一个函数,输入一个二叉树,该函数输出它的镜像。

    例如输入:

    4
    /
    2 7
    / /
    1 3 6 9
    镜像输出:

    4
    /
    7 2
    / /
    9 6 3 1

    左右子树交换

    class Solution {
    public:
        TreeNode* mirrorTree(TreeNode* root) {
            if(root == NULL) return root;
            preOrder(root);
            return root;
        }
        void preOrder(TreeNode* root){
            if(root -> left) preOrder(root -> left);
            if(root -> right) preOrder(root -> right);
            if(root -> left ==NULL && root -> right == NULL) return;
            swap(root -> left,root -> right);
        }
    };
    

    对称的二叉树

    class Solution {
    public:
        bool isSymmetric(TreeNode* root) {
            if(root == NULL) return true;
            return preOrder(root,root);
        }
        bool preOrder(TreeNode *a,TreeNode *b){
            if(a == NULL && b == NULL) return true;
            if(a == NULL || b == NULL) return false;
            if(a -> val != b -> val) return false;
            return preOrder(a -> left,b -> right) && preOrder(a -> right,b -> left);
        }
    };
    

    顺时针打印矩阵

    设定上下左右限制,每次都是遍历边上的

    class Solution {
    public:
        vector<int> spiralOrder(vector<vector<int>>& matrix) {
            vector<int> ans;
            if(matrix.empty()) return ans;
            if(matrix[0].empty()) return ans;
            int rows = matrix.size(),cols = matrix[0].size(); 
            int left = 0 ,right = cols - 1,up = 0,down = rows -1;
            int num = rows*cols;
            while(true){
                int i;
                for(i=left;i<=right;++i) ans.push_back(matrix[up][i]);
                up++;
                if(up>down) break;
                for(i=up;i<=down;++i) ans.push_back(matrix[i][right]);
                right--;
                if(right<left) break;
                for(i=right;i>=left;--i) ans.push_back(matrix[down][i]);
                down--;
                if(down<up) break;
                for(i=down;i>=up;--i) ans.push_back(matrix[i][left]);
                left++;
                if(left>right) break;
            }
            return ans;
        }
    };
    

    解决面试题的思路-举例让抽象问题具体化

    包含min函数的栈

    我写了个动态数组2333

    如果要求min必须是(O(1))需要用一个辅助数组

    class MinStack {
    private:
    int Size;
    int *numStack,*minStack,*tmpStack1,*tmpStack2;
    int tail;
    public:
        /** initialize your data structure here. */
        MinStack() {
            Size = 1;
            numStack = new int[Size];
            minStack = new int[Size];
            tail = -1;
        }
        
        void push(int x) {
            tail++;
            if(tail == Size){
                Size <<= 1;
                tmpStack1 = new int[Size];
                tmpStack2 = new int[Size];
                for(int i = 0;i < tail;++i){
                    tmpStack1[i] = numStack[i];
                    tmpStack2[i] = minStack[i];
                }
                std::swap(tmpStack1,numStack);
                std::swap(tmpStack2,minStack);
                delete []tmpStack1;
                delete []tmpStack2;
            }
            numStack[tail] = x;
            if(tail == 0) minStack[tail] = x;
            else minStack[tail] = std::min(x,minStack[tail-1]);
        }
        
        void pop() {
            tail--;
        }
        
        int top() {
            return numStack[tail];
        }
        
        int min() {
            return minStack[tail];
        }
    };
    
    /**
     * Your MinStack object will be instantiated and called as such:
     * MinStack* obj = new MinStack();
     * obj->push(x);
     * obj->pop();
     * int param_3 = obj->top();
     * int param_4 = obj->min();
     */
    

    栈的压入、弹出序列

    输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

    想好再写,思路清晰

    要举出几个例子

    class Solution {
    public:
        bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
            if(pushed.empty()) return true;
            int stackLen = pushed.size();
            int *tmpStack = new int[stackLen],tmpLen = 0,popPos = 0;
            for(int i = 0;i < stackLen; ++i){
                tmpStack[tmpLen++] = pushed[i];
                while(tmpLen&&tmpStack[tmpLen - 1] == popped[popPos]){
                    tmpLen--;
                    popPos++;
                }
            }
            delete []tmpStack;
            return tmpLen == 0;
        }
    };
    

    从上到下打印出二叉树

    • 考察广搜

    从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

    class Solution {
    public:
        vector<int> levelOrder(TreeNode* root) {
            std::queue<TreeNode*> printQue;
            vector<int> ans;
            if(root == NULL) return ans;
            printQue.push(root);
            while(!printQue.empty()){
                TreeNode* tmp = printQue.front();
                printQue.pop();
                ans.push_back(tmp -> val);
                if(tmp -> left) printQue.push(tmp -> left);
                if(tmp -> right) printQue.push(tmp -> right);
            }
            return ans;
        }
    };
    

    从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

    class Solution {
    public:
        vector<vector<int>> levelOrder(TreeNode* root) {
            std::queue<TreeNode*> printQue;
            vector<vector<int> >ans;
            if(root == NULL) return ans;
            printQue.push(root);
            while(!printQue.empty()){
                vector<int>cnt;
                int Size = printQue.size();
                while(Size--) {
                    TreeNode* tmp = printQue.front();
                    printQue.pop();
                    cnt.push_back(tmp -> val);
                    if(tmp -> left) printQue.push(tmp -> left);
                    if(tmp -> right) printQue.push(tmp -> right);
                }
                ans.push_back(cnt);
            }
            return ans;
        }
    };
    

    请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

    可以按照上面的做法,然后reverse

    更高效率的是用栈和队列,或者双端队列模拟

    class Solution {
    public:
        vector<vector<int>> levelOrder(TreeNode* root) {
            std::stack<TreeNode*> printStk;
            queue<TreeNode*>printQue;
            vector<vector<int> >ans;
            if(root == NULL) return ans;
            printStk.push(root);
            bool flag = 0;
            while(!printStk.empty()){
                vector<int>cnt;
                while(!printStk.empty()) {
                    TreeNode* tmp = printStk.top();
                    printStk.pop();
                    printQue.push(tmp);
                    cnt.push_back(tmp -> val);
                }
                while(!printQue.empty()) {
                    TreeNode* tmp = printQue.front();
                     printQue.pop();
                     if(!flag){
                        if(tmp -> left) printStk.push(tmp -> left);
                        if(tmp -> right) printStk.push(tmp -> right);
                     }else{
                         if(tmp -> right) printStk.push(tmp -> right); 
                         if(tmp -> left) printStk.push(tmp -> left);
                     }
                }
                flag ^= 1;
                ans.push_back(cnt);
            }
            return ans;
        }
    };
    

    解决面试题的思路-分解让复杂问题简单化

    复杂链表的复制

    请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

    可以用哈希的方法

    但这种方法浪费空间,更好的方法是在原来的基础上修改

    需要复制某个结点,val和next好复制,random不好复制

    必须要新旧结点建立某种联系就好办了,因为我想马上知道一个结点的另一个copy结点,那就先将copy结点插入到原链表中。

    class Solution {
    public:
        Node* copyRandomList(Node* head) {
            if(head == NULL) return head;
            Node* nowNode = head;
            while(head != NULL){
                Node* newNode = new Node(head -> val);
                newNode -> next = head -> next;
                head -> next = newNode;
                head = newNode -> next;
            }
            head = nowNode;
            while(head != NULL){
                if(head -> random != NULL) head -> next -> random = head -> random -> next;
                head = head ->next -> next;
            }
            head = nowNode;
            Node *newHead = NULL;
            while(head != NULL){
                nowNode = head -> next;
                head -> next = nowNode ->next;
                if(newHead == NULL){
                    newHead = nowNode;
                }
                if(head -> next != NULL) nowNode -> next = head -> next ->next;
                head = head -> next;
            }
            return newHead;
        }
    };
    

    二叉搜索树与双向链表

    把二叉搜索树转为双向链表

    做法其实就是中序遍历

    记录一下中序遍历上一个访问的结点

    class Solution {
    public:
        Node* treeToDoublyList(Node* root) {
            Node* head = NULL,*tail = NULL;
            if(root == NULL) return head;
            change(root,tail,head);
            head -> left = tail;
            tail -> right = head;
            Node *nowhead = head;
            return head;
        }
        void change(Node* now,Node* &last,Node* &head){
            if(now -> left) change(now ->left,last,head);
    
            if(last != NULL){
                last -> right = now;
                now -> left = last;
            }
            else head = now;
            last = now;
    
            if(now->right) change(now -> right,last,head);
        } 
    };
    

    序列化二叉树

    按照层序遍历来序列化。

    stringstream可以关注一下

    class Codec {
    public:
    
        // Encodes a tree to a single string.
        string serialize(TreeNode* root) {
            ostringstream out;
            queue<TreeNode*> q;
            q.push(root);
            while (!q.empty()) {
                TreeNode* tmp = q.front();
                q.pop();
                if (tmp) {
                    out<<tmp->val<<" ";
                    q.push(tmp->left);
                    q.push(tmp->right);
                } else {
                    out<<"null ";
                }
            }
            return out.str();
        }
    
        // Decodes your encoded data to tree.
        TreeNode* deserialize(string data) {
            istringstream input(data);
            string val;
            vector<TreeNode*> vec;
            while (input >> val) {
                if (val == "null") {
                    vec.push_back(NULL);
                } else {
                    vec.push_back(new TreeNode(stoi(val)));
                }
            }
            int j = 1;                                          // i每往后移动一位,j移动两位,j始终是当前i的左子下标
            for (int i = 0; j < vec.size(); ++i) {              // 肯定是j先到达边界,所以这里判断j < vec.size()
                if (vec[i] == NULL) continue;                   // vec[i]为null时跳过。
                if (j < vec.size()) vec[i]->left = vec[j++];    // 当前j位置为i的左子树
                if (j < vec.size()) vec[i]->right = vec[j++];   // 当前j位置为i的右子树
            }
            return vec[0];
        }
    
    };
    

    字符串的排列

    将问题分解为当前字符与后面的字符,接下来就是求后面字符的排列。

     *  [a, [b, c]]
     * [b, [a, c]] [c, [b, a]]
     *
     * 如上,对字符串"abc"分割,每次固定一个字符为一部分,
     * 其他字符为另一部分,再将固定字符与其他字符进行交换,
     * 依次遍历每个字符,再进行回溯递归。
    
    class Solution {
    public:
        vector<string> permutation(string s) {
            vector<string> ans;
            if(s.size() == NULL) return ans;
             map<string,bool>mp;
            dfs(mp,ans,s,0);
            return ans;
        }
        void dfs(map<string,bool>& mp,vector<string>&ans,string& a,int pos){
            if(pos == a.size()){
                if(mp.count(a) == 0) {
                    ans.push_back(a);
                    mp[a] = 1;
                }
                return;
            }
            for(int i = pos;i < a.size();++i){
                swap(a[pos],a[i]);
                dfs(mp,ans,a,pos+1);
                swap(a[i],a[pos]);
            }
        }
    };
    

    优化时间和空间效率-时间效率

    数组中出现次数超过一半的数字

    可以用哈希表

    class Solution {
    public:
        int majorityElement(vector<int>& nums) {
            map<int,int>table;
            int maxx = 0,t =1,tmp;
            for(int x:nums){
                table[x]++;
                tmp = table[x];
                if(tmp>maxx){
                    maxx = tmp;
                    t = x;
                }
            }
            return t;
        }
    };
    

    但其实有更加高效的做法,因为次数超过一半,这个数列可以分解为等于众数的和不等于众数的

    两两抵消,多出来的一定是众数

    关注一下摩尔投票

    最小的k个数

    大根堆

    class Solution {
    public:
        vector<int> getLeastNumbers(vector<int>& arr, int k) {
            priority_queue<int>que; vector<int>vt;
            if(!k) return vt;
            for(int x:arr){
               if(que.size() < k) que.push(x);
               else if(x < que.top()){
                   que.pop();
                   que.push(x);
               }
            }
           
            while(!que.empty()){
                vt.push_back(que.top());
                que.pop();
            }
            return vt;
        }
    };
    

    数据流中的中位数

    一种方法是维护两个堆,一个是大根堆,一个是小根堆

    关键是大根堆的大小与小根堆的差距不超过1

    并且小根堆的值一定比大根堆大

    class MedianFinder {
    private:
        priority_queue<int>queL;
        priority_queue<int,vector<int>,greater<int> >queR;
    public:
        /** initialize your data structure here. */
        MedianFinder() {
    
        }
        
        void addNum(int num) {
            if(queL.size() <= queR.size()){
                queL.push(num);
            }else{
                queR.push(num);
            }
    
            if(queL.empty() || queR.empty()) return;
    
            if(queL.top()>queR.top()){
                int x = queL.top();
                queL.pop(); 
                queR.push(x);
    
                x = queR.top(); 
                queR.pop();
                queL.push(x);
            }
        }
        
        double findMedian() {
            if((queL.size()+queR.size())&1){
                return queL.top()*1.0;
            }else{
                return (queL.top() + queR.top())/2.0;
            }
        }
    };
    
    

    连续子数组的最大和

    动态规划

    当前的最优解:如果加上前面的和比本身要小,那就不要加,从这个位置重新开始

    class Solution {
    public:
        int maxSubArray(vector<int>& nums) {
            int ans = nums[0],len = nums.size();
            for(int i=1;i<len;++i){
                nums[i] += max(0,nums[i-1]);
                ans = max(nums[i],ans);
            }
            return ans;
        }
    };
    

    1-n整数中1出现的次数

    从最高位考虑,分为最高位中的1和其他位上1

    举例的方法: 23456

    最高位是1的为[10000,19999]一共是20000个

    然后再考虑大于3456的1出现个数

    可分为[3457,9999]∪[20000,23456] 和[10000,19999]两个区间

    然后在4位中选择1位是1,其余位随便。排列组合:(2*4*10^3)

    class Solution {
    public:
        int countDigitOne(int n) {
            return solve(n);
            
        }
        int solve(int n){
            string str = to_string(n);
    
            int highNum = str[0] - '0'; 
             int strLen = str.size();
            if(strLen == 1 && highNum == 0) return 0;
            if(strLen == 1 && highNum > 1) return 1;
    
           
            int withoutHigh = n - highNum * pow(10,strLen-1);
           
            int ans = 0;
            if(highNum > 1){
                ans += pow(10,strLen - 1);
            }else if(highNum == 1){
                ans += withoutHigh + 1;
            }
    
            ans += highNum * (strLen -1) *pow(10,strLen -2);
            return ans += solve(withoutHigh);
    
        } 
    };
    

    还有找规律的方法:

    https://blog.csdn.net/qq_22873427/article/details/78159057

    数字序列中某一位的数字

    数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

    请写一个函数,求任意第n位对应的数字。

    找规律的题目,要找到属于哪个区间

    按照位数来划分区间

    class Solution {
    public:
        int findNthDigit(int n) {
            if(n == 0) return 0;
            n--;
            long long k = 9,x = 1,cnt = 1;
            while(n > x*k){
                n -= k*x;
                x++;
                k*=10;
                cnt *= 10;
            }
            int p = cnt + n/x;
            string str = to_string(p);
            return str[n%x] - '0'; 
        }
    };
    

    把数组排成最小的数

    输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

    直接排序就可。但是有个问题就是并不是字典序最小的放在前面最优:3,30

    所以在cmp函数里可以这么用:return a+b<b+a

    记得cmp前面要加static

    理由:https://blog.csdn.net/qq_43827595/article/details/104242037

    class Solution {
    public:
        static bool cmp(const string &x,const string &y){
            return x + y < y + x;
        }
        string minNumber(vector<int>& nums) {
            vector<string>vt;
            for(int x:nums){
                vt.push_back(to_string(x));            
            }
            sort(vt.begin(),vt.end(),cmp);
            string ans;
            for(string x:vt){
                ans += x;
            }
            return ans;
        }
    };
    

    礼物的最大价值

    在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

    最基本的那种dp

    class Solution {
    public:
        int maxValue(vector<vector<int>>& grid) {
            int rows = grid.size(),cols = grid[0].size();
            for(int i = 0;i < rows;++i){
                for(int j = 0;j < cols;++j){
                    if(i == 0 && j == 0) continue;
                    if(i == 0) grid[i][j] += grid[i][j-1];
                    else if(j == 0) grid[i][j] += grid[i-1][j];  
                    else grid[i][j] = max(grid[i-1][j],grid[i][j-1]) + grid[i][j];
                }
            }
            return grid[rows-1][cols-1];
        }
    };
    

    把数字翻译成字符串

    给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

    一个简单dp

    class Solution {
    public:
        int translateNum(int num) {
            string s = to_string(num);
            int strLen = s.size();
            int *dp = new int[strLen]{0};
            dp[0] = 1;
            for(int i=1;i<strLen;++i){
                if(s[i-1]!='0'&&(s[i-1]-'0')*10+s[i]-'0' < 26){
                    if(i == 1) dp[i] = 2;
                    else dp[i] = dp[i-1] + dp[i-2];
                }else{
                    dp[i] = dp[i-1];
                }
            }
            return dp[strLen - 1];
        }
    };
    

    最长不含重复字符的子字符串

    用一个滑动窗口来模拟一个双端队列

    队列里面维护的是一个没有重复字符的字符串

    class Solution {
    public:
        int lengthOfLongestSubstring(string s) {
            int sLength = s.size(),ans=0;
            if(sLength == 0) return 0;
            int *que = new int [sLength]{0},head =0,tail=0;
            bool *vis = new bool[256]{0};
            for(int i = 0;i<sLength;++i){
                if(vis[s[i]] == 0){
                    que[tail++] = s[i];
                    vis[s[i]] = 1;
                }else{
                    while(head < tail && que[head] != s[i]){
                        vis[que[head]] = 0;
                        head++;
                    }
                    head++;
                    que[tail++] = s[i];
                }
                ans = max(ans,tail-head);
            }
            return ans;
        }
    };
    

    优化时间和空间效率-时间效率与空间效率的平衡

    丑数

    我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

    可以发现每个丑数都是比它前面每个丑数x2x3x5得来的

    class Solution {
    public:
        int nthUglyNumber(int n) {
            int *dp = new int[n]{0},p2 = 0,p3 = 0,p5 = 0;
            dp[0] = 1;
            for(int i = 1;i < n;++i){
                dp[i] = min(min(dp[p2]*2,dp[p3]*3),dp[p5]*5);
                if(dp[i] == dp[p2]*2) p2++;
                if(dp[i] == dp[p3]*3) p3++;
                if(dp[i] == dp[p5]*5) p5++;
            }
            return dp[n - 1];
        }
    };
    

    其实这个方法有些难想到,可以采用队列的方法,用三个队列,这样就可以避免重复

    第一个只出现一次的字符

    哈希map水题

    class Solution {
    public:
        char firstUniqChar(string s) {
            int *hashTable = new int[256]{0};
            if(s =="") return ' ';
            int strLen = s.size();
            for(int i=0;i<strLen;++i){
                hashTable[s[i]]++;
            }
            for(int i = 0;i<strLen;++i){
                if(hashTable[s[i]] == 1){
                    return s[i];
                }
            }
            return ' ';
        }
    };
    

    数组中的逆序对

    直接归并排序

    class Solution {
    public:
        int reversePairs(vector<int>& nums) {
            if(nums.size() == 0) return 0;
            int numsLength = nums.size();
            int ans = 0;
            vector<int>tmp(numsLength);
            Msort(0,numsLength - 1,nums,ans,tmp);
            return ans;
        }
        void Msort(int l,int r,vector<int>& nums,int &ans,vector<int>& tmp){
            if(l == r) return;
            int mid = (l+r) >> 1;
            Msort(l,mid,nums,ans,tmp);
            Msort(mid + 1,r,nums,ans,tmp);
            Megre(l,r,nums,ans,tmp);
        }
        void Megre(int l,int r,vector<int>& nums,int &ans,vector<int>& tmp){
            int mid = (l+r) >> 1,i = l,j = mid + 1,p = l;
            while(i <= mid && j <= r ){
                if(nums[i] <= nums[j]) {
                    tmp[p++] = nums[i++];
                }else{
                    ans += mid - i + 1;
                    tmp[p++] = nums[j++];
                }
            }
           while(i <= mid){
               tmp[p++] = nums[i++];
           } 
           while(j <= r){
               tmp[p++] = nums[j++];
           }
           for(int k=l;k<=r;++k) nums[k] = tmp[k];
        }
    };
    

    两个链表的第一个公共结点

    求出两个长度就好做了

    其实还可以用双指针O(n+m)的方法

    class Solution {
    public:
        ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
            if(headA == nullptr || headB == nullptr) return nullptr;
            int lenA = getLen(headA),lenB = getLen(headB);
            while(headA != nullptr && headB != nullptr){
                if(lenA > lenB) headA = headA -> next,lenA--;
                else if(lenA <lenB) headB = headB -> next,lenB--;
                else{
                    if(headA == headB) return headA;
                    headA = headA -> next;
                    headB = headB -> next;
                }
            }
            return nullptr;
        }
        inline int getLen(ListNode* head){
            int ans = 0;
            while(head != nullptr){
                ans++;
                head = head -> next;
            }
            return ans;
        }
    };
    

    面试中的各项能力-知识迁移能力

    在排序数组中查找数字

    直接二分

    class Solution {
    public:
        int search(vector<int>& nums, int target) {
            int len = nums.size();
            int l = 0,r = len - 1;
            while(l < r){
                int mid = (l + r) >> 1;
                if(nums[mid] >= target){
                    r = mid;
                }else{
                    l = mid + 1;
                }
            }
            int ans = 0;
            while(l < len && nums[l] == target){
                ans++;
                l++;
            }
            return ans;
        }
    };
    

    0 - n-1中缺失的数字

    二分的条件为是否前面包括自己已经出现了缺失

    前0后1查找最小的1

    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
           int len = nums.size();
            int l = 0,r = len - 1;
            while(l < r){
                int mid = (l + r) >> 1;
                if(nums[mid] != mid){
                    r = mid;
                }else{
                    l = mid + 1;
                }
            }
            if(nums[l] == l) l++;
            return l;
        }
    };
    

    二叉搜索树的第k大节点

    反过来的中序遍历

    class Solution {
    public:
        int kthLargest(TreeNode* root, int k) {
            int ans;
            midOrder(root,k,ans);
            return ans;
        }
        void midOrder(TreeNode* root,int &k,int &ans){
            if(root -> right) midOrder(root -> right,k,ans);
            k--;
            if(k == 0) ans = root -> val;
            if(root -> left) midOrder(root -> left,k,ans);
        }
    };
    

    二叉树的深度

    水题

    class Solution {
    public:
        int maxDepth(TreeNode* root) {
            if(root == nullptr) return 0;
            return dfs(root);
        }
        int dfs(TreeNode* root){
            int dep = 0;
            if(root -> left) dep = max(dep,dfs(root -> left));
            if(root -> right) dep = max(dep,dfs(root -> right));
            return dep + 1; 
        }
    };
    

    平衡二叉树

    然鹅这题只是判断一下是不是平衡二叉树

    class Solution {
    public:
        bool isBalanced(TreeNode* root) {
            if(root == nullptr) return 1;
            bool flag = 1;
            dfs(root,flag);
            return flag;
        }
         int dfs(TreeNode* root,bool &flag){
            int l = 0,r = 0;
            if(root -> left) l = dfs(root -> left,flag);
            if(root -> right) r = dfs(root -> right,flag);
            if(abs(l-r) > 1) flag = 0;
            return max(l,r) + 1; 
        }
    };
    

    数组中数字出现的次数

    一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

    如果只出现一次,那么其实可以采用异或和的方法

    现在出现两次,异或和之后得到的值是两个数的异或和

    然后找到第一个异或和二进制位上的某个1,1代表的是这两个数不同的地方

    然后再将这个数组分类

    class Solution {
    public:
        vector<int> singleNumbers(vector<int>& nums) {
            int xorSum = 0,lorSum = 0,rorSum = 0,k = 0,numsLen = nums.size();
            for(int i = 0;i<numsLen;++i){
                xorSum ^= nums[i];
            }
            while(xorSum % 2 == 0) k++,xorSum >>= 1;
            for(int i = 0;i<numsLen;++i){
                if((nums[i]>>k)&1){
                    lorSum ^= nums[i];
                }else{
                    rorSum ^= nums[i];
                }
            }
            vector<int>ans;
            ans.push_back(lorSum);
            ans.push_back(rorSum);
            return ans;
        }
    };
    

    在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

    按照二进制位,对每个位出现的个数%3

    如果是0说明不是这数,否则是

    这样的复杂度是O(n)的,空间复杂度O(1);

    class Solution {
    public:
        int singleNumber(vector<int>& nums) {
            int *bit = new int [31]{0},numsLength = nums.size();
            for(int i=0;i<numsLength;++i){
                for(int j = 30;j>=0;--j){
                    if((nums[i]>>j)&1) bit[j]++;
                }
            }
            int ans = 0;
            for(int i = 30;i >= 0;--i){
                if(bit[i]%3 != 0){
                    ans += 1<<i;
                } 
            }
            delete []bit;
            return ans;
        }
    };
    

    和为s的两个数

    输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

    单调性,双指针。

    如果此时决策空间里最大的和最小的加在一起比目标大,说明最大的那个会被排除

    反之同理

    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
            int firstP = 0,lastP = nums.size() - 1;
            vector<int>ans;
            while(firstP<lastP){
                if(nums[firstP] + nums[lastP] > target){
                    lastP--;
                }else if(nums[firstP] + nums[lastP] < target){
                    firstP ++;
                }else{
                    ans.push_back(nums[firstP]);
                    ans.push_back(nums[lastP]);
                    break;
                }
            }
            return ans; 
        }
    };
    

    和为s的连续正数序列

    跟上题一样采用双指针

    首先第一个数肯定不可以超过target/2

    如果等比数列求和得到的数超过target,那么首项肯定没用了,firstP++

    不到的话自然是lastP++

    class Solution {
    public:
        vector<vector<int>> findContinuousSequence(int target) {
            long long firstP = 1,lastP = 1;
            long long  len = target/2,sum = 0;
            vector<vector<int> >ans;
            while(firstP <= len){
                sum = (lastP-firstP+1)*firstP + (lastP-firstP+1)*(lastP-firstP)/2;
                if(sum == target) {
                    vector<int>tmp;
                    for(int i=firstP;i<=lastP;++i){
                        tmp.push_back(i);
                    }
                    ans.push_back(tmp);
                }
                if(sum <= target){
                    lastP++;
                }else {
                    firstP++;
                }
            }
            return ans;
        }
    };
    

    翻转单词顺序

    输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。

    每个单词翻转一次,然后整体翻转一次

    注意它的输入有多余的空格

    class Solution {
    public:
        string reverseWords(string s) {
            int k = 0;
            for (int i = 0; i < s.size(); ++ i){
                while (i < s.size() && s[i] == ' ') ++i;  //找到第一个非空格字符
                if (i == s.size()) break;
                int j = i;
                while (j < s.size() && s[j] != ' ') ++j;    //遍历1个非空单词
                reverse(s.begin() + i, s.begin() + j);      //反转1个单词
                if (k) s[k++] = ' ';
                while (i < j) s[k++] = s[i++];      //反转后的1个单词赋给s[k]
            }
            s.erase(s.begin() + k, s.end());   //删除 k后面的东西
            reverse(s.begin(), s.end());
            return s;
        }
    };
    

    左旋转字符串

    可以采用取模的方法

    class Solution {
    public:
        string reverseLeftWords(string s, int n) {
            string S = "";
            int len = s.length();
            for(int i = 0; i < len; i++){
                int x = (i + n) % len;
                S += s[x];
            }
            return S;
        }
    };
    

    用string模拟

    class Solution {
    public:
        string reverseLeftWords(string s, int n) {
            for(int i = 0;i<n;++i){
                s += s[i];
            }
            s.erase(s.begin(),s.begin()+n);
            return s;
        }
    };
    

    滑动窗口的最大值

    维护一个递减队列

    class Solution {
    public:
        vector<int> maxSlidingWindow(vector<int>& nums, int k) {
            int numsLen = nums.size();
            int *que = new int[numsLen];
            int head = 0,tail = 0;
            vector<int>ans;
            for(int i=0;i<numsLen;++i){ 
                while(head < tail && nums[que[tail-1]] <= nums[i]) tail--;
                que[tail++] = i;
                while(head < tail && que[head] < i - k + 1) head++;
                if(i >= k - 1) ans.push_back(nums[que[head]]); 
            }
            delete []que;
            return ans;
        }
    };
    

    队列的最大值

    请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_valuepush_backpop_front均摊时间复杂度都是O(1)。

    若队列为空,pop_frontmax_value 需要返回 -1

    维护一个递减的队列

    准确的说是不降的队列

    这样就不会删除多了

    class MaxQueue {
    private:
        queue<int>q;
        deque<int>d;
    public:
        MaxQueue() {
    
        }
        
        int max_value() {
            if(d.empty()) return -1;
            return d.front();
        }
        
        void push_back(int value) {
            while(!d.empty() && d.back() < value){
                d.pop_back();
            }
            d.push_back(value); 
            q.push(value);
        }
        
        int pop_front() {
            if(q.empty()) return -1;
            int val = q.front();
            if(val == d.front()) d.pop_front();
            q.pop();
            return val;
        }
    };
    

    面试中的各项能力-建模抽象能力

    n个骰子的点数

    把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

    动态规划,因为只与上一个状态有关,所以可以化为一维的dp

    转移也比较简单,枚举当前可能的值

    class Solution {
    public:
        vector<double> twoSum(int n) {
           int dp[70];
           memset(dp,0,sizeof(dp));
            for(int i = 1;i<=6;++i) dp[i] = 1;
            for(int i = 2;i <= n;++i){
                for(int j = 6*i;j >= i; --j){
                    dp[j] = 0;
                    for(int z = 1;z<=6;++z){
                        if(j - z < i-1) break;//注意边界
                        dp[j] += dp[j - z]; 
                    }
                }
            }
            vector<double>ans;
            double all = pow(6,n);
            for(int i = n;i<=6*n;++i){
                ans.push_back(dp[i]*1.0/all);
            }
            return ans;
        }
    };
    

    扑克牌中的顺子

    水题

    class Solution {
    public:
        bool isStraight(vector<int>& nums) {
            sort(nums.begin(),nums.end());
            int k=0;
            for(int i=0;i<5;++i){
                if(nums[i]==0) k++;
            }
            for(int i=0;i<4;++i){
                if(nums[i]!=0){
                    if(nums[i+1] == nums[i]) return false;
                    if(nums[i+1] != nums[i]+1){
                        k -= nums[i+1]-nums[i]-1;
                    }
                }
                if(k<0) return false;
            }
            return true;
        }
    };
    

    圆圈中最后剩下的数字

    0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

    例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

    这是约瑟夫环的模板题

    具体证明待更

    class Solution {
    public:
        int lastRemaining(int n, int m) {
            int flag = 0;   
        for (int i = 2; i <= n; i++) {
            flag = (flag + m) % i;
        }
        return flag;
        }
    };
    

    股票的最大利润

    贪心水题

    得知比当前的价格小就行了

    class Solution {
    public:
        int maxProfit(vector<int>& prices) {
            int len = prices.size();
            if(len <= 1) return 0;
            int ans = 0,minn = prices[0];
            for(int i=1;i<len;++i){
                if(prices[i] >= minn) ans = max(ans,prices[i]-minn); 
                minn = min(minn,prices[i]);
            }
            return ans;
        }
    };
    

    面试中的各项能力-发散思维能力

    求1+2+...n

    1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

    用递归和&&

    class Solution {
    public:
        int sumNums(int n) {
            n&&(n+=sumNums(n-1));
            return n;
        }
    };
    

    不用加减乘除做加法

    很明显是二进制瞎搞

    两个数取并运算就是进位的数,异或运算就是加之后没有进位的数

    循环一下就好了

    class Solution {
    public:
        int add(int a, int b) {
            while (b) {
                int carry = (unsigned int)(a & b) << 1;
                a ^= b;
                b = carry;
            }
            return a;
        }
    };
    

    构建乘积数组

    给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

    构造前缀积和后缀积

    class Solution {
    public:
        vector<int> constructArr(vector<int>& a) {
            int n = a.size();
            vector<int> ret(n, 1);
            int left = 1;
            for (int i = 0; i < n; i ++) {
                ret[i] = left;
                left = left * a[i];
            } 
            int right = 1;
            for (int i = n-1; i >= 0; i --) {
                ret[i] *= right;
                right *= a[i];
            }
            return ret;
        }
    };
    

    把字符串转换成整数

    模拟题

    class Solution {
    public:
        int strToInt(string str) {
            int i=0,len = str.size();
            long long flag = 1;
            while(str[i] == ' ') ++i;
            if(str[i] == '-') flag = -1,++i;
            else if(str[i] == '+') ++i;
            if(!isdigit(str[i])) return 0;
            long long ans = 0;
            while(i < len){
                if(!isdigit(str[i])) break;
                ans = ans * 10 + str[i]-'0';
                if(ans*flag >= INT_MAX) return INT_MAX;
                if(ans*flag <= INT_MIN) return INT_MIN;
                ++i;
            }
            return ans*flag;
        }
    };
    

    二叉搜索树的最近公共祖先

    因为是二叉搜索树,所以可以充分利用性质

    class Solution {
    public:
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            if(p == q) return p;
            if(p -> val > q -> val) swap(p,q);
            while(true){
                if(root -> left && root -> val > q -> val){
                    root = root ->left;
                }else if(root -> right && root -> val < p -> val ){
                    root = root -> right;
                }else{
                    return root;
                }
            }
            return nullptr;
        }
    };
    

    二叉树的最近公共祖先

    在没有指向父亲的指针的情况下,可以采用辅助数组记录路径,因为数都是不同的

    也可以采用后序遍历,判断子树是否遍历到相关节点

    对于比这两个节点深度浅节点,无非有两种情况。一是这两个节点在同一侧,二是在两侧。

    只需后序遍历判断一下左右子树中有无相关节点即可。如果两边都有,那就返回该节点,如果只有一边有,那就返回子树的答案。

    class Solution {
    public:
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            return behindOrder(root,p,q);
        }
        TreeNode* behindOrder(TreeNode* root,TreeNode* p,TreeNode* q){
            if(root == p) return root;
            if(root == q) return root;
    
            TreeNode* l1 = nullptr,*l2 = nullptr;
            if(root -> left) l1 = behindOrder(root -> left,p,q);
            if(root -> right) l2 = behindOrder(root -> right,p,q);
    
            if(l1 && l2) return root;
            if(l1 != nullptr) return l1;
            if(l2 != nullptr) return l2; 
            return nullptr;
        }
    };
    
  • 相关阅读:
    Android中Acition和Category常量表
    android系统各种音量的获取与设置
    Swift中数组和字典都是值类型
    iOS中的日期和时间
    iOS中的日历
    UILabel的富文本显示选项
    使用NSURLProtocol和NSURLSession拦截UIWebView的HTTP请求(包括ajax请求)
    Error Domain=NSURLErrorDomain Code=-1202,Https服务器证书无效
    iOS计算完整文字高度(适应iOS 10)
    NSUserDefaults存取失败
  • 原文地址:https://www.cnblogs.com/smallocean/p/12487468.html
Copyright © 2020-2023  润新知