zoukankan      html  css  js  c++  java
  • 剑指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;
        }
    };
    
  • 相关阅读:
    bzoj-2748 2748: [HAOI2012]音量调节(dp)
    bzoj-2338 2338: [HNOI2011]数矩形(计算几何)
    bzoj-3444 3444: 最后的晚餐(组合数学)
    codeforces 709E E. Centroids(树形dp)
    codeforces 709D D. Recover the String(构造)
    codeforces 709C C. Letters Cyclic Shift(贪心)
    codeforces 709B B. Checkpoints(水题)
    codeforces 709A A. Juicer(水题)
    Repeat Number
    hdu 1003 Max Sum (动态规划)
  • 原文地址:https://www.cnblogs.com/smallocean/p/12487468.html
Copyright © 2011-2022 走看看