剑指offer题目总结

文章目录

数据结构

数组

13. 找出数组中重复的数字

给定一个长度为 n 的整数数组 nums,数组中所有的数字都在 0∼n−1 的范围内。

数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。

请找出数组中任意一个重复的数字。

注意:如果某些数字不在 0∼n−1 的范围内,或数组中不包含重复数字,则返回 -1;

样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。

返回 2 或 3。

交换,O(n)

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n = nums.size();
        for(int i : nums) if(i < 0 || i >= n) return -1;
        for(int i = 0; i < n; i++)
        {
            int num = nums[i];
            if(i == num) continue;
            if(nums[num] == num) return num;
            swap(nums[num], nums[i]);
        }
        return -1;
    }
};

14. 不修改数组找出重复的数字

给定一个长度为 n+1 的数组nums,数组中所有的数均在 1∼n 的范围内,其中 n≥1。

请找出数组中任意一个重复的数,但不能修改输入的数组。

样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。

返回 2 或 3。
思考题:如果只能使用 O(1) 的额外空间,该怎么做呢?

数的范围在1~n之间。按照mid分为两个区间,[l,mid] [mid+1, r]这两个数的范围内肯定会有重复。计算所有数落在这两个区间内的个数,如果多了,说明肯定是这个区间里的数重复了。O(nlogn)

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int l = 1, r = nums.size() - 1;
        // 数的范围从1-n
        while(l < r)
        {
            int mid = l + r >> 1;
            // [l, mid] [mid + 1, r]两个范围的数
            int s = 0;
            // 计算所有数据中处于[l, mid]这个范围的数的个数是多少
            for(int i : nums)
                if(i >= l && i <= mid)
                    s++;
            // 如果【l,mid】这个范围内的数大于mid-l+1就说明多的数是在这个范围中的
            if(s > mid - l + 1) r = mid;
            else l = mid + 1;
        }
        return l;
    }
};

15. 二维数组中的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。

请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

样例
输入数组:

[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]

如果输入查找数值为7,则返回true,

如果输入查找数值为5,则返回false。

主要是寻找单调性。左上右下的点有单调性。每次都会少一行或一列。复杂度是O(n+m)

class Solution {
public:
    bool searchArray(vector<vector<int>> a, int t) {
        if(a.empty() || a[0].empty()) return false;
        int i = 0, j = a[0].size() - 1;
        while(i < a.size() && j >= 0)
        {
            if(a[i][j] == t) return true;
            if(a[i][j] > t) j--;
            else i++;
        }
        return false;
    }
};

字符串

16. 替换空格

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

你可以假定输入字符串的长度最大是1000。
注意输出字符串的长度可能大于1000。

样例
输入:“We are happy.”

输出:“We%20are%20happy.”

这道题用string没啥意思。书里说是先分配空间,然后从后往前复制。

class Solution {
public:
    string replaceSpaces(string &str) {
        int len = 0;
        for(char c :str)
            if(c == ' ') len += 3;
            else len += 1;
        
        int j = len - 1, i = str.size() - 1;
        str.resize(len);
        
        while(i >= 0)
        {
            if(str[i] == ' ')
            {
                str[j--] = '0';
                str[j--] = '2';
                str[j--] = '%';
            }
            else str[j --] = str[i];
            
            i--;
        }
        return str;
    }
};

链表

17. 从尾到头打印链表

输入一个链表的头结点,按照 从尾到头 的顺序返回节点的值。

返回的结果用数组存储。

样例
输入:[2, 3, 5]
返回:[5, 3, 2]

  1. 遍历然后reverse
  2. 用栈记录结果
  3. 递归
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> printListReversingly(ListNode* head) {
        vector<int> ret;
        ListNode* ptr = head;
        while(ptr)
        {
            ret.push_back(ptr->val);
            ptr = ptr->next;
        }
        reverse(ret.begin(), ret.end());
        return ret;
    }
};

反转链表

定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。

样例
输入:1->2->3->4->5->NULL

输出:5->4->3->2->1->NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        while(head)
        {
            ListNode* next = head->next;
            head->next = pre;a
            pre = head;
            head = next;
        }
        return pre;
    }
};

18. 重建二叉树

输入一棵二叉树前序遍历和中序遍历的结果,请重建该二叉树。

注意:

二叉树中每个节点的值都互不相同;
输入的前序遍历和中序遍历一定合法;
样例
给定:
前序遍历是:[3, 9, 20, 15, 7]
中序遍历是:[9, 3, 15, 20, 7]

返回:[3, 9, 20, null, null, 15, 7, null, null, null, null]
返回的二叉树如下所示:
3
/
9 20
/
15 7

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> pre, in;
    unordered_map<int, int> inpos;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        pre = preorder;
        in = inorder;
        for(int i = 0; i < in.size(); i++) inpos[in[i]] = i;
        
        return build(0, pre.size() - 1, 0, in.size() - 1);
    }
    
    TreeNode* build(int prest, int preed, int inst, int ined)
    {
        if(prest > preed) return NULL;
        int rootval = pre[prest];
        int inmid = inpos[rootval];
        int leftlen = inmid - inst;
        int rightlen = ined - inmid; 
        
        TreeNode* u = new TreeNode(rootval);
        u->left = build(prest + 1, prest + leftlen, inst, inmid - 1);
        u->right = build(prest + leftlen + 1, preed, inmid + 1, ined);
        return u;
    }
};

19. 二叉树的下一个节点

给定一棵二叉树的其中一个节点,请找出中序遍历序列的下一个节点。

注意:

如果给定的节点是中序遍历序列的最后一个,则返回空节点;
二叉树一定不为空,且给定的节点一定不是空节点;
样例
假定二叉树是:[2, 1, 3, null, null, null, null], 给出的是值等于2的节点。

则应返回值等于3的节点。

解释:该二叉树的结构如下,2的后继节点是3。
2
/
1 3

  1. 有右节点
    1. 右子树最左边的
  2. 一路向上,如果朝着左边拐了一下,那拐过去的父节点就是下一个节点。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode *father;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL), father(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* p) {
        if(!p) return NULL;
        if(p->right)
        {
            TreeNode* next = p->right;
            while(next->left) next = next->left;
            return next;
        }
        while(p->father && p->father->right == p) p = p->father;
        if(p->father) return p->father;
        return NULL;
    }
};

栈和队列

20. 用两个栈实现队列

请用栈实现一个队列,支持如下四种操作:

push(x) – 将元素x插到队尾;
pop() – 将队首的元素弹出,并返回该元素;
peek() – 返回队首元素;
empty() – 返回队列是否为空;
注意:

你只能使用栈的标准操作:push to top,peek/pop from top, size 和 is empty;
如果你选择的编程语言没有栈的标准库,你可以使用list或者deque等模拟栈的操作;
输入数据保证合法,例如,在队列为空时,不会进行pop或者peek等操作;
样例
MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false

一个栈不断往另一个栈倒就行。

class MyQueue {
private:
    stack<int> in, out;
public:
    /** Initialize your data structure here. */
    MyQueue() {
        
    }
    
    /** Push element x to the back of queue. */
    void push(int x) {
        in.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        if(out.size())
        {
            int v = out.top();
            out.pop();
            return v;
        }
        while(in.size())
        {
            out.push(in.top());
            in.pop();
        }
        return pop();
    }
    
    /** Get the front element. */
    int peek() {
        if(out.size())
        {
            int v = out.top();
            return v;
        }
        while(in.size())
        {
            out.push(in.top());
            in.pop();
        }
        return peek();
    }
    
    /** Returns whether the queue is empty. */
    bool empty() {
        return in.empty() && out.empty();
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * bool param_4 = obj.empty();
 */

算法和数据操作

递归和循环

21. 斐波那契数列

输入一个整数 n ,求斐波那契数列的第 n 项。

假定从0开始,第0项为0。(n<=39)

样例
输入整数 n=5

返回 5

class Solution {
public:
    int Fibonacci(int n) {
        int f[n + 1] = {0};
        f[0] = 0;
        f[1] = 1;
        for(int i = 2; i <= n; i++) f[i] = f[i - 1] + f[i - 2];
        return f[n];
    }
};

查找和排序

22. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

输入一个升序的数组的一个旋转,输出旋转数组的最小元素。

例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。

数组可能包含重复项。

注意:数组内所含元素非负,若数组大小为0,请返回-1。

样例
输入:nums=[2,2,2,0,1]

输出:0

在这里插入图片描述
这道题是具有二分的性质的。左边区间都大于等于第一个元素(当然要把右边区间最左边几个排除掉)然后二分找右边区间的左端点就好。

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

回溯

23. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。

路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。

如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。

注意:

输入的路径不为空;
所有出现的字符均为大写英文字母;
样例
matrix=
[
[“A”,“B”,“C”,“E”],
[“S”,“F”,“C”,“S”],
[“A”,“D”,“E”,“E”]
]

str=“BCCE” , return “true”

str=“ASAE” , return “false”

遍历+dfs

class Solution {
public:
    int n, m;
    bool st[1000][1000];
    
    bool dfs(vector<vector<char>>& matrix, string &str, int x, int y, int u)
    {
        if(u >= str.size()) return true;
        int dx[4] = {1, -1, 0, 0} ,dy[4] = {0, 0, 1, -1};
        st[x][y] = true;
        
        for(int i = 0; i < 4; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            if(a < 0 || a >= n || b < 0 || b >= m || st[a][b] || matrix[a][b] != str[u]) continue;
            if(dfs(matrix, str, a, b, u + 1)) return true;
        }
        return false;
    }
    
    bool hasPath(vector<vector<char>>& matrix, string &str) {
        n = matrix.size();
        if(!n) return false;
        m = matrix[0].size();
        if(!m) return false;
        memset(st, false, sizeof st);
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                if(matrix[i][j] == str[0])
                    if(dfs(matrix, str, i, j, 1))
                        return true;
                    else
                        memset(st, false, sizeof st);
        return false;
    }
};

24. 机器人的运动范围

地上有一个 m 行和 n 列的方格,横纵坐标范围分别是 0∼m−1 和 0∼n−1。

一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格。

但是不能进入行坐标和列坐标的数位之和大于 k 的格子。

请问该机器人能够达到多少个格子?

样例1
输入:k=7, m=4, n=5

输出:20
样例2
输入:k=18, m=40, n=40

输出:1484

解释:当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。
但是,它不能进入方格(35,38),因为3+5+3+8 = 19。
注意:

0<=m<=50
0<=n<=50
0<=k<=100

class Solution {
public:
    bool check(int a, int b, int k)
    {
        int t = 0;
        while(a)
        {
            t += a % 10;
            a /= 10;
        }
        while(b)
        {
            t += b % 10;
            b /= 10;
        }
        return t <= k;
    }
    int movingCount(int threshold, int rows, int cols)
    {
        if(rows == 0 || cols == 0) return 0;
        typedef pair<int, int> PII;
        queue<PII> q;
        q.push({0, 0});
        bool st[rows][cols];
        memset(st, false, sizeof st);
        int cnt = 0;
        
        while(q.size())
        {
            auto t = q.front(); q.pop();
            int x = t.first, y = t.second;
            if(st[x][y]) continue;
            st[x][y] = true;
            if(check(x, y, threshold)) cnt++;
            
            int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
            for(int i = 0; i < 4; i++)
            {
                int a = x + dx[i], b = y + dy[i];
                if(a < 0 || a >= rows || b < 0 || b >= cols || st[a][b] || !check(a, b, threshold)) continue;
                q.push({a, b});
            }
        }
        return cnt;
    }
};

动态规划和贪心

25. 剪绳子

给你一根长度为 n 绳子,请把绳子剪成 m 段(m、n 都是整数,2≤n≤58 并且 m≥2)。

每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]k[1] … k[m] 可能的最大乘积是多少?

例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。

样例
输入:8

输出:18

f(i)表示长度为i的绳子所有被剪的方法中乘积最大的那种。f(i)分为(0, i - j) (i-j, i)两段。后面长度为j的这一段不去动它。前面(i-j)这一段可剪可不剪。然后遍历每个分界点j。

class Solution {
public:
    int maxProductAfterCutting(int n) {
        int dp[n + 1];
        memset(dp, 0, sizeof dp);
        for(int i = 2; i <= n; i++)
            for(int j = 1; j < i; j++)
                dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j));
        return dp[n];
    }
};

位运算

26. 二进制中1的个数

输入一个32位整数,输出该数二进制表示中1的个数。

注意:

负数在计算机中用其绝对值的补码来表示。
样例1
输入:9
输出:2
解释:9的二进制表示是1001,一共有2个1。
样例2
输入:-2
输出:31
解释:-2在计算机里会被表示成11111111111111111111111111111110,
一共有31个1。

class Solution {
public:
    int NumberOf1(int n) {
        int res = 0;
        for(int i = 31; i >= 0; i--)
        {
            res += (n >> i) & 1;
        }
        return res;
    }
};

代码的完整性

27. 数值的整数次方

实现函数double Power(double base, int exponent),求base的 exponent次方。

不得使用库函数,同时不需要考虑大数问题。

注意:

不会出现底数和指数同为0的情况
样例1
输入:10 ,2

输出:100
样例2
输入:10 ,-2

输出:0.01

主要考虑负数情况

class Solution {
public:
    double Power(double base, int e) {
        double res = 1;
        int sign = e > 0 ? 1 : -1;
        e = abs(e);
        while(e)
        {
            if(e & 1) res *= base;
            e >>= 1;
            base = base * base;
        }
        if(sign < 0) res = 1 / res;
        return res;
    }
};

28. 在O(1)时间删除链表结点

给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。

假设链表一定存在,并且该节点一定不是尾节点。

样例
输入:链表 1->4->6->8
删掉节点:第2个节点即6(头节点为第0个节点)

输出:新链表 1->4->8

交换数据,跳过,然后删除

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* u) {
        swap(u->val, u->next->val);
        ListNode* next = u->next;
        u->next = next->next;
        delete next;
    }
};

29. 删除链表中重复的节点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留。

样例1
输入:1->2->3->3->4->4->5

输出:1->2->5
样例2
输入:1->1->1->2->3

输出:2->3

因为可能删掉头节点。所以要建立一个虚拟节点。每次都去看接下来这个数是不是只有一个。如果不是就直接跳过这一段。p指向每一段数字的前一个位置。q寻找下一段数字的开始位置。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplication(ListNode* head) {
        auto dummy = new ListNode(-1);
        dummy->next = head;
        auto p = dummy;
        
        while(p->next)
        {
            auto q = p->next;
            while(q && p->next->val == q->val) q = q->next;
            
            if(p->next->next == q) p = p->next;
            else p = q;
        }
        return dummy->next;
    }
};

30. 正则表达式匹配

请实现一个函数用来匹配包括’.‘和’*'的正则表达式。

模式中的字符’.‘表示任意一个字符,而’*'表示它前面的字符可以出现任意次(含0次)。

在本题中,匹配是指字符串的所有字符匹配整个模式。

例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配。

样例
输入:

s=“aa”
p=“a*”

输出:true

这道题真的挺不错的。f(i, j)表示s的i以后所有字母和p的j以后所有字母是否相同。

如果si == pj,或者pj == ‘.’。那i,j可以匹配。f(i, j) = f(i+1, j+1) && (si == pj || pj == ‘.’)

如果pj+1 == ‘*’。那就有两种情况:

  1. 不匹配,f(i, j + 2)
  2. 匹配,f(i + 1, j)。虽然说能够匹配多个,但是也无需遍历,这种情况会递归算出所有匹配多个的情况。
class Solution {
public:
    int n, m;
    int f[1000][1000];
    bool isMatch(string s, string p) {
        memset(f, -1, sizeof f);
        n = s.size();
        m = p.size();
        return dp(0, 0, s, p);
    }
    
    bool dp(int x, int y, string& s, string& p)
    {
        if(f[x][y] != -1) return f[x][y];
        if(y == m) return f[x][y] = x == n;
        
        bool ismatch = (x < n) && (s[x] == p[y] || p[y] == '.');
        bool ans;
        if(y + 1 < m && p[y + 1] == '*')
            ans = dp(x, y + 2, s, p) || // 不匹配
                    ismatch && dp(x + 1, y, s, p) ; // 匹配一个字符
        else
            ans = ismatch && dp(x + 1, y + 1, s, p);
        return f[x][y] = ans;
    }
};

31. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。

但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

注意:

小数可以没有整数部分,例如.123等于0.123;
小数点后面可以没有数字,例如233.等于233.0;
小数点前面和后面可以有数字,例如233.666;
当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4;
样例:
输入: “0”

输出: true

这道题我觉得没啥意思,不做了。

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

输入一个整数数组,实现一个函数来调整该数组中数字的顺序。

使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。

样例
输入:[1,2,3,4,5]

输出: [1,3,5,2,4]

双指针

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int n = array.size();
        int l = 0, r = n - 1;
        while(l < r)
        {
            while(l < r && array[r] % 2 == 0) r--;
            while(l < r && array[l] % 2 == 1) l++;
            if(l < r) swap(array[l], array[r]);
        }
    }
};

代码鲁棒性

33. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个结点。

注意:

k >= 0;
如果k大于链表长度,则返回 NULL;
样例
输入:链表:1->2->3->4->5 ,k=2

输出:4

快慢指针,统计长度,快指针先走k步。然后快慢一起走,快指针走到底的时候返回慢指针就好。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* findKthToTail(ListNode* head, int k) {
        int n = 0;
        ListNode* u = head;
        while(u)
        {
            n++;
            u = u->next;
        }
        if(k > n) return NULL;
        
        ListNode* slow = head, *fast = head;
        while(k-- && fast) fast = fast->next;
        while(fast && slow)
        {
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

34. 链表中环的入口结点

给定一个链表,若其中包含环,则输出环的入口节点。

若其中不包含环,则输出null。

样例
QQ截图20181202023846.png

给定如上所示的链表:
[1, 2, 3, 4, 5, 6]
2
注意,这里的2表示编号是2的节点,节点编号从0开始。所以编号是2的节点就是val等于3的节点。

则输出环的入口节点3.

  1. 判定有没有环?快慢指针看会不会追尾
  2. 快慢指针相遇之后,让一个指针回到起点。两个指针一步一步走,相遇的时候就是环的入口

证明:假设快慢指针在c点相遇。慢指针往后退y步,快指针往后退2y步。可以发现x+y肯定是圈的周长的倍数。所以在c处再走x步就正好是一个周长,正好就能走到b处。
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *entryNodeOfLoop(ListNode *head) {
        auto slow = head, fast = head;
        while(slow && fast)
        {
            slow = slow->next;
            fast = fast->next;
            if(fast) fast = fast->next;
            else return NULL;
            
            if(slow == fast)
            {
                slow = head;
                while(slow != fast)
                {
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        }
        return NULL;
    }
};

35. 反转链表

定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。

样例
输入:1->2->3->4->5->NULL

输出:5->4->3->2->1->NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        while(head)
        {
            ListNode* next = head->next;
            head->next = pre;a
            pre = head;
            head = next;
        }
        return pre;
    }
};

36. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。

样例
输入:1->3->5 , 2->4->5

输出:1->2->3->4->5->5

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* merge(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(-1);
        ListNode* p = dummy;
        while(l1 || l2)
        {
            if(l1 && l2)
            {
                if(l1->val <= l2->val)
                {
                    p->next = l1;
                    l1 = l1->next;
                }
                else
                {
                    p->next = l2;
                    l2 = l2->next;
                }
            }
            else if(l1)
            {
                p->next = l1;
                l1 = l1->next;
            }
            else 
            {
                p->next = l2;
                l2 = l2->next;
            }
            p = p->next;
            p->next = NULL;
        }
        
        return dummy->next;
    }  
};

37. 树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。

我们规定空树不是任何树的子结构。

样例
树A:

 8
/ \

8 7
/
9 2
/
4 7
树B:

8
/
9 2
返回 true ,因为B是A的子结构。

这道题还比较有意思。递归地来做比较简单。思路就是遍历树地每一个节点。每个节点都尝试和目标子树匹配一下。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool hasSubtree(TreeNode* u1, TreeNode* u2) {
        if(!u1 || !u2) return false;
        if(check(u1, u2)) return true;
        return hasSubtree(u1->left, u2) || hasSubtree(u1->right, u2);
    }
    bool check(TreeNode* u1, TreeNode* u2)
    {
        if(!u2) return true;
        if(!u1 || u1->val != u2->val ) return false;
        return check(u1->left, u2->left) && check(u1->right, u2->right);
    }
};

比较抽象的问题

38. 二叉树的镜像

输入一个二叉树,将它变换为它的镜像。

样例
输入树:
8
/
6 10
/ \ /
5 7 9 11

[8,6,10,5,7,9,11,null,null,null,null,null,null,null,null]
输出树:
8
/
10 6
/ \ /
11 9 7 5

[8,10,6,11,9,7,5,null,null,null,null,null,null,null,null]

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    void mirror(TreeNode* u) {
        if(!u) return;
        swap(u->left, u->right);
        mirror(u->left);
        mirror(u->right);
    }
};

39. 对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。

如果一棵二叉树和它的镜像一样,那么它是对称的。

样例
如下图所示二叉树[1,2,2,3,4,4,3,null,null,null,null,null,null,null,null]为对称二叉树:
1
/
2 2
/ \ /
3 4 4 3

如下图所示二叉树[1,2,2,null,4,4,3,null,null,null,null,null,null]不是对称二叉树:
1
/
2 2
\ /
4 4 3

这道题也还不错,递归来做比较简单。要对称的话,左边和右边相等,右边也和左边相等。递归的时候这两种情况都要考虑到。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* u) {
        if(!u) return true;
        return check(u, u);
    }
    bool check(TreeNode* p1, TreeNode* p2)
    {
        if(!p1 || !p2) return !p1 && !p2;
        return p1->val == p2->val && check(p1->left, p2->right) && check(p1->right, p2->left);
    }
};

40. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

样例
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]

输出:[1,2,3,4,8,12,11,10,9,5,6,7]

经典问题。记录好边界值和访问过的值就行。每次一个方向走到底之后变换方向就行。

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> res;
        int n = matrix.size();
        if(!n) return res;
        int m = matrix[0].size();
        int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
        int dir = 0, x = 0, y = 0;
        vector<vector<bool>> st(n, vector<bool>(m, false));
        for(int i = 0; i < n * m; i++)
        {
            res.push_back(matrix[x][y]);
            st[x][y] = true;
            int a = x + dx[dir], b = y + dy[dir];
            if(a < 0 || a >= n || b < 0 || b >= m || st[a][b])
            {
                dir = (dir + 1) % 4;
                a = x + dx[dir], b = y + dy[dir];
            }
            x = a, y = b;
        }
        return res;
    }
};

41. 包含min函数的栈

设计一个支持push,pop,top等操作并且可以在O(1)时间内检索出最小元素的堆栈。

push(x)–将元素x插入栈中
pop()–移除栈顶元素
top()–得到栈顶元素
getMin()–得到栈中最小元素
样例
MinStack minStack = new MinStack();
minStack.push(-1);
minStack.push(3);
minStack.push(-4);
minStack.getMin(); --> Returns -4.
minStack.pop();
minStack.top(); --> Returns 3.
minStack.getMin(); --> Returns -1.

颇有一点单调栈的感觉。维护一个最小值栈,和栈同步,不过记录的是相同高度位置处的最小值。

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {
        
    }
    
    void push(int x) {
        stk.push(x);
        if(mnstk.empty())
            mnstk.push(x);
        else
            mnstk.push(min(x, mnstk.top()));    
    }
    
    void pop() {
        stk.pop();
        mnstk.pop();
    }
    
    int top() {
        return stk.top();
    }
    
    int getMin() {
        return mnstk.top();
    }
    stack<int> stk, mnstk;
};

/**
 * 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.getMin();
 */

42. 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。

假设压入栈的所有数字均不相等。

例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

注意:若两个序列长度不等则视为并不是一个栈的压入、弹出序列。若两个序列都为空,则视为是一个栈的压入、弹出序列。

样例
输入:[1,2,3,4,5]
[4,5,3,2,1]

输出:true

这道题的核心就是。如果栈顶和出栈序列要匹配的位置一样,就一定要出栈。否则这个位置肯定会被其他字符占用,肯定就匹配不了了。

class Solution {
public:
    bool isPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.size() != popV.size()) return false;
        
        stack<int> stk;
        int i = 0;
        for(int v : pushV)
        {
            stk.push(v);
            while(stk.size() && stk.top() == popV[i])
            {
                i++;
                stk.pop();
            }
        }
        return i == popV.size();
    }
};

43. 不分行从上往下打印二叉树

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

样例
输入如下图所示二叉树[8, 12, 2, null, null, 6, null, 4, null, null, null]
8
/
12 2
/
6
/
4

输出:[8, 12, 2, 6, 4]

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> printFromTopToBottom(TreeNode* root) {
        vector<int> ret;
        if(!root) return ret;
        queue<TreeNode*> q;
        q.push(root);
        while(q.size())
        {
            auto t = q.front(); q.pop();
            ret.push_back(t->val);
            if(t->left) q.push(t->left);
            if(t->right) q.push(t->right);
        }
        return ret;
    }
};

44. 分行从上往下打印二叉树

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

样例
输入如下图所示二叉树[8, 12, 2, null, null, 6, null, 4, null, null, null]
8
/
12 2
/
6
/
4

输出:[[8], [12, 2], [6], [4]]

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> printFromTopToBottom(TreeNode* root) {
        vector<vector<int>> ret;
        if(!root) return ret;
        queue<TreeNode*> q;
        q.push(root);

        while(q.size())
        {
            int len = q.size();
            vector<int> tmp;
            while(len --)
            {
                auto t = q.front(); q.pop();
                tmp.push_back(t->val);
                if(t->left) q.push(t->left);
                if(t->right) q.push(t->right);
            }
            ret.push_back(tmp);
        }
        return ret;
    }
};

45. 之字形打印二叉树

请实现一个函数按照之字形顺序从上向下打印二叉树。

即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

样例
输入如下图所示二叉树[8, 12, 2, null, null, 6, 4, null, null, null, null]
8
/
12 2
/
6 4
输出:[[8], [2, 12], [6, 4]]

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> printFromTopToBottom(TreeNode* root) {
        vector<vector<int>> ret;
        if(!root) return ret;
        queue<TreeNode*> q;
        q.push(root);
        
        bool rev = false;
        while(q.size())
        {
            int len = q.size();
            vector<int> tmp;
            while(len --)
            {
                auto t = q.front(); q.pop();
                tmp.push_back(t->val);
                if(t->left) q.push(t->left);
                if(t->right) q.push(t->right);
            }
            if(rev) reverse(tmp.begin(), tmp.end());
            rev = !rev;
            ret.push_back(tmp);
        }
        return ret;
    }
};

46. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。

如果是则返回true,否则返回false。

假设输入的数组的任意两个数字都互不相同。

样例
输入:[4, 8, 6, 12, 16, 14, 10]

输出:true

因为这是搜索树。后序遍历最后一个节点肯定是树根。然后前面的肯定分成两段。前一段比后一段大。如果发现大小由交错了,肯定不是后序遍历序列。然后递归判断两个子树就行。

class Solution {
public:
    vector<int> seq;
    bool verifySequenceOfBST(vector<int> sequence) {
        seq = sequence;
        return dfs(0, seq.size() - 1);
    }
    
    bool dfs(int l, int r)
    {
        if(l >= r) return true;
        int root = seq[r];
        int k = l;
        while(k < r && seq[k] < root) k++;
        for(int i = k; i < r; i++)
            if(seq[i] < root)
                return false;
        return dfs(l, k - 1) && dfs(k, r - 1);
    }
};

47. 二叉树中和为某一值的路径

输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。

从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

样例
给出二叉树如下所示,并给出num=22。
5
/
4 6
/ /
12 13 6
/ \ /
9 1 5 1

输出:[[5,4,12,1],[5,6,6,5]]

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> findPath(TreeNode* root, int sum) {
        vector<vector<int>> ret;
        vector<int> path;
        dfs(root, sum, 0, path, ret);
        return ret;
    }
    
    void dfs(TreeNode* u, int target, int cur, vector<int>& path, vector<vector<int>>& ret)
    {
        if(!u) return;
        
        cur += u->val;
        path.push_back(u->val);
        
        if(!u->left && !u->right && cur == target) ret.push_back(path);
        
        if(u->left) dfs(u->left, target, cur, path, ret);
        if(u->right) dfs(u->right, target, cur, path, ret);
        
        
        path.pop_back();
    }
};

记录从树根到树根路径的数值就行。

48. 复杂链表的复刻

请实现一个函数可以复制一个复杂链表。

在复杂链表中,每个结点除了有一个指针指向下一个结点外,还有一个额外的指针指向链表中的任意结点或者null。

注意:

函数结束后原链表要与输入时保持一致。

这道题有多种思路。比较简单的就是用一个哈希表记录映射关系。还有一个比较巧妙地方法。就是先在每个节点后面添加一个复制地节点。然后在有random节点地地方p->next->random=p->random->next。最后再把复制的节点全都摘下来就行。确实比较巧妙

/**
 * Definition for singly-linked list with a random pointer.
 * struct ListNode {
 *     int val;
 *     ListNode *next, *random;
 *     ListNode(int x) : val(x), next(NULL), random(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *copyRandomList(ListNode *head) {
        for(auto p = head; p; )
        {
            auto np = new ListNode(p->val);
            auto next = p->next;
            p->next = np;
            np->next = next;
            p = next;
        }
        
        for(auto p = head; p; p = p->next->next)
        {
            if(p->random)
                p->next->random = p->random->next;
        }
        
        auto dummy = new ListNode(-1);
        auto cur = dummy;
        for(auto p = head; p; )
        {
            cur->next = p->next;
            cur = cur->next;
            p->next = p->next->next;
            cur->next = NULL;
            p = p->next;
        }
        return dummy->next;
    }
};

49. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。

要求不能创建任何新的结点,只能调整树中结点指针的指向。

注意:

需要返回双向链表最左侧的节点。
例如,输入下图中左边的二叉搜索树,则输出右边的排序双向链表。

QQ截图20181202052830.png

这道题也是也比较巧妙。可以写一个递归函数。返回子树的最右边和最左边的节点。然后把树根和两个子树左右两个相连。返回的时候返回最左和最右的节点就好。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* convert(TreeNode* root) {
        if(!root) return NULL;
        auto ret = dfs(root);
        return ret.first;
    }
    
    pair<TreeNode*, TreeNode*> dfs(TreeNode* u)
    {
        if(!u->left && !u->right) return {u, u};
        if(u->left && u->right)
        {
            auto l = dfs(u->left), r = dfs(u->right);
            l.second->right = u; u->left = l.second;
            r.first->left = u; u->right = r.first;
            return {l.first, r.second};
        }
        if(u->left)
        {
            auto l = dfs(u->left);
            l.second->right = u; u->left = l.second;
            return {l.first, u};
        }
        auto r = dfs(u->right);
        r.first->left = u; u->right = r.first;
        return {u, r.second};
        
    }
};

50. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

您需要确保二叉树可以序列化为字符串,并且可以将此字符串反序列化为原始树结构。

样例
你可以序列化如下的二叉树
8
/
12 2
/
6 4

为:"[8, 12, 2, null, null, 6, 4, null, null, null, null]"
注意:

以上的格式是AcWing序列化二叉树的方式,你不必一定按照此格式,所以可以设计出一些新的构造方式。

这道题我只能说stringstream真的好用啊。哈哈哈。序列化和反序列化按照同样的顺序就可以。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        ostringstream out;
        dfs(root, out);
        string ret = out.str();
        return ret;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        istringstream in(data);
        return dfs2(in);
    }
    
    void dfs(TreeNode* u, ostringstream& out)
    {
        if(!u)
        {
            out << " null ";
            return ;
        }
        out << u->val << ' ';
        dfs(u->left, out);
        dfs(u->right, out);
    }
    
    TreeNode* dfs2(istringstream& in)
    {
        string str;
        in >> str;
        if(str == "null") return NULL;
        int val = stoi(str);
        auto root = new TreeNode(val);
        root->left = dfs2(in);
        root->right = dfs2(in);
        return root;
        return NULL;
    }
};

51. 数字排列

输入一组数字(可能包含重复数字),输出其所有的排列方式。

样例
输入:[1,2,3]

输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

class Solution {
public:
    vector<bool> st;
    void dfs(vector<int>& nums, vector<int>& path, vector<vector<int>>& ret)
    {
        if(path.size() == nums.size())
        {
            ret.push_back(path);
            return ;
        }
        
        for(int i = 0; i < nums.size(); i++)
        {
            if(st[i]) continue;
            if(i > 0 && !st[i - 1] && nums[i - 1] == nums[i]) continue;
            st[i] = true;
            path.push_back(nums[i]);
            dfs(nums, path, ret);
            path.pop_back();
            st[i] = false;
        }
        
    }
    vector<vector<int>> permutation(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<int> path;
        vector<vector<int>> ret;
        int n = nums.size();
        st = vector<bool>(n, false);
        dfs(nums, path, ret);
        return ret;
    }
};

直接dfs

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

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

假设数组非空,并且一定存在满足条件的数字。

思考题:

假设要求只能使用 O(n) 的时间和额外 O(1) 的空间,该怎么做呢?
样例
输入:[1,2,1,1,3]

输出:1

class Solution {
public:
    int moreThanHalfNum_Solution(vector<int>& nums) {
        int cnt = 1, num = nums[0];
        for(int i = 1; i < nums.size(); i++)
        {
            if(nums[i] == num) cnt++;
            else cnt--;
            if(cnt == 0) 
            {
                num = nums[i];
                cnt = 1;
            }
        }
        return num;
    }
};

53. 最小的k个数

输入n个整数,找出其中最小的k个数。

注意:

数据保证k一定小于等于输入数组的长度;
输出数组内元素请按从小到大顺序排序;
样例
输入:[1,2,3,4,5,6,7,8] , k=4

输出:[1,2,3,4]

要求前k个比较小的数。因为要求按顺序输出。就不太好用快排。可以用一个大顶堆。加入堆之后如果长度大于k就pop出堆顶。

class Solution {
public:
    vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
        priority_queue<int> q;
        for(int i : input)
        {
            q.push(i);
            if(q.size() > k) q.pop();
        }
        vector<int> ret(k);
        for(int i = k - 1; i >= 0; i--)
        {
            ret[i] = q.top();
            q.pop();
        }
        return ret;
    }
};

AcWing 54. 数据流中的中位数

如何得到一个数据流中的中位数?

如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。

如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

样例
输入:1, 2, 3, 4

输出:1,1.5,2,2.5

解释:每当数据流读入一个数据,就进行一次判断并输出当前的中位数。
这道题确实比较经典。

思路就是用大顶堆存储小的一半数。用小顶堆存储大的一半数。每次都默认插入大顶堆。如果大顶堆堆顶比小顶堆堆顶大了,就交换一下。如果大顶堆比小顶堆的个数大了1了,就把往小顶堆里放一个。

class Solution {
public:
    priority_queue<int> mxheap;
    priority_queue<int, vector<int>, greater<int>> mnheap;
    void insert(int num){
        mxheap.push(num);
        if(mnheap.size() && mxheap.top() > mnheap.top())
        {
            int mnv = mnheap.top(), mxv = mxheap.top();
            mnheap.pop();
            mxheap.pop();
            mnheap.push(mxv);
            mxheap.push(mnv);
        }
        if(mxheap.size() > mnheap.size() + 1)
        {
            mnheap.push(mxheap.top());
            mxheap.pop();
        }
    }

    double getMedian(){
        if(mxheap.size()+ mnheap.size() & 1) return mxheap.top();
        else return (mxheap.top() + mnheap.top()) / 2.0;
    }
};

55. 连续子数组的最大和

输入一个 非空 整型数组,数组里的数可能为正,也可能为负。

数组中一个或连续的多个整数组成一个子数组。

求所有子数组的和的最大值。

要求时间复杂度为O(n)。

样例
输入:[1, -2, 3, 10, -4, 7, 2, -5]

输出:18

其实这是一道dp。只不过用一个变量就行。sum就表示走到i这个位置最大的连续子数组和是多少。如果sum>0了,当前数就可以加到sum。否则当前数替换sum。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int sum = 0, mx = INT_MIN;
        for(int i : nums)
        {
            if(sum > 0) sum += i;
            else sum = i;
            mx = max(mx, sum);
        }
        return mx;
    }
};

56. 从1到n整数中1出现的次数

输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。

例如输入12,从1到12这些整数中包含“1”的数字有1,10,11和12,其中“1”一共出现了5次。

样例
输入: 12
输出: 5

数位统计问题。对于一个数abcdef。要看c这一位有多少个1。首先前两个数取0~ab-1的时候,后面两个数随便取。这就有ab*100种。前两个数取ab的时候。如果c=0,那肯定是啥都取不了了。如果c=1,后面只能取0—ef-1。如果c>1,后面两个数随便取,就有100种。

class Solution {
public:
    int numberOf1Between1AndN_Solution(int n) 
    {
        vector<int> digits;
        while(n) digits.push_back(n % 10), n /= 10;
        reverse(digits.begin(), digits.end());
        int res = 0;
        for(int i = 0; i < digits.size(); i++)
        {
            int left = 0, right = 0, power = 1;
            for(int j = 0; j < i; j++) left = left * 10 + digits[j];
            for(int j = i + 1; j < digits.size(); j++) right = right * 10 + digits[j], power *= 10;
            res += left * power;
            if(digits[i] == 1) res += right + 1;
            else if (digits[i] > 1) res += power;
        }
        return res;
    }
};

57. 数字序列中某一位的数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。

在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。

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

样例
输入:13

输出:1

这是一道数位统计问题。考虑到个位数有9个,十位数有90个,百位数有900个。个位数占1位,十位数占两位,百位数占3位。根据这个规律就可以求出到底是几位数。然后三位数的开始数字是100,n每三位组成一个数。所以就有 n/i ( i 表示是 i 位数),这里要下取整。具体是这个数字里的第几位就是n%i,这里如果n=i就说明是最后一位。

class Solution {
public:
    int digitAtIndex(int n) {
        // 算出是几位数
        long long l = 1, s = 9, base = 1;
        while(n > l * s)
        {
            n -= l * s;
            l ++;
            s *= 10;
            base *= 10;
        }
        
        // 算出是 l 位数里面的第几个
        int number = base + (n + l - 1) / l - 1;
        
        // 算是 number 这个数的第几位
        int r = n % l ? n % l : l;
        
        for(int i = 0; i < l - r; i++) number /= 10;
        return number % 10;
    }
};

58. 把数组排成最小的数

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

例如输入数组[3, 32, 321],则打印出这3个数字能排成的最小数字321323。

样例
输入:[3, 32, 321]

输出:321323
注意:输出数字的格式为字符串。

这道题非常巧妙。就是定义比较函数。a<b <=> ab < ba。这个比较函数是满足反对称性和传递性的。所以按照这个比较函数得到的最后序列也是最小的。

class Solution {
public:
    static bool cmp(int a, int b)
    {
        string as = to_string(a), bs = to_string(b);
        return as + bs < bs + as;
    }
    string printMinNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end(), cmp);
        string res;
        for(int i : nums) res += to_string(i);
        return res;
    }
};

59. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:

0翻译成”a”,1翻译成”b”,……,11翻译成”l”,……,25翻译成”z”。

一个数字可能有多个翻译。例如12258有5种不同的翻译,它们分别是”bccfi”、”bwfi”、”bczi”、”mcfi”和”mzi”。

请编程实现一个函数用来计算一个数字有多少种不同的翻译方法。

样例
输入:“12258”

输出:5

这是一道dp题目。f(i)表示到i这个位置一共有多少中翻译方法。f(i)可以由f(i-1)和f(i-2)转移过来的。前一个一定可以转移。后一个只有前两个字母满足条件的时候才能转移。

class Solution {
public:
    int getTranslationCount(string s) {
        int n = s.size();
        vector<int> f(n + 1, 0);
        f[0] = 1;
        for(int i = 1; i <= n; i++)
        {
            f[i] += f[i - 1];
            if(i > 1)
            {
                int pre = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');
                if(pre >= 10 && pre <= 25)
                    f[i] += f[i - 2];
            }
        }
        return f[n];
    }
};

60. 礼物的最大价值

在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。

你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。

给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?

注意:

m,n>0
样例:

输入:
[
[2,3,1],
[1,7,1],
[4,6,1]
]

输出:19

解释:沿着路径 2→3→7→6→1 可以得到拿到最大价值礼物。

也是dp题目。

f(i, j)表示走到i,j最大值是多少。可以由左边和上面的最大值转移过来。

class Solution {
public:
    int getMaxValue(vector<vector<int>>& grid) {
        int n = grid.size();
        if(!n) return 0;
        int m = grid[0].size();
        
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < m; j++)
            {
                int up = 0;
                int left = 0;
                if(i > 0) up = grid[i - 1][j];
                if(j > 0) left = grid[i][j - 1];
                grid[i][j] += max(up, left);
            }
        }
        return grid[n - 1][m - 1];
    }
};

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

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

假设字符串中只包含从’a’到’z’的字符。

样例
输入:“abcabc”

输出:3

这是一个双指针。如果加入字符之后出现了重复,就只能让左指针不断移动直至无重复。

class Solution {
public:
    int longestSubstringWithoutDuplication(string s) {
        int mp[256] = {0};
        int n = s.size();
        int res = 0;
        for(int r = 0, l = 0; r < n; r++)
        {
            mp[s[r]]++;
            while(mp[s[r]] > 1)
            {
                mp[s[l++]]--;
            }
            res = max(res, r - l + 1);
        }
        return res;
    }
};

62. 丑数

我们把只包含质因子2、3和5的数称作丑数(Ugly Number)。

例如6、8都是丑数,但14不是,因为它包含质因子7。

求第n个丑数的值。

样例
输入:5

输出:5
注意:习惯上我们把1当做第一个丑数。

这道题挺好的。i2,i3,i5都指向1。下一个丑数肯定是这三个因子能够形成的最小数。生成下一个丑数之后,可以生成这个丑数的那个因子的坐标往前走一个。这样每一步都能保证三个因子产生的丑数一定包含下一个丑数。

class Solution {
public:
    int getUglyNumber(int n) {
        if(n <= 1) return 1;
        int i = 0, j = 0, k = 0;
        vector<int> f(1, 1);
        while(--n)
        {
            long long t = min(f[i] * 2, min(f[j] * 3, f[k] * 5));
            if(t == f[i] * 2) i++;
            if(t == f[j] * 3) j++;
            if(t == f[k] * 5) k++;
            f.push_back(t);
        }
        return f.back();
    }
};

63. 字符串中第一个只出现一次的字符

在字符串中找出第一个只出现一次的字符。

如输入"abaccdeff",则输出b。

如果字符串中不存在只出现一次的字符,返回#字符。

样例:
输入:“abaccdeff”

输出:‘b’

class Solution {
public:
    char firstNotRepeatingChar(string s) {
        int mp[256] = {0};
        for(char ch : s) mp[ch] ++;
        for(char ch : s) if(mp[ch] == 1) return ch;
        return '#';
    }
};

64. 字符流中第一个只出现一次的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。

例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是’g’。

当从该字符流中读出前六个字符”google”时,第一个只出现一次的字符是’l’。

如果当前字符流没有存在出现一次的字符,返回#字符。

样例
输入:“google”

输出:“ggg#ll”

解释:每当字符流读入一个字符,就进行一次判断并输出当前的第一个只出现一次的字符。

重复字母是指前面所有字符的。所以记录字符出现的次数。队列中保存指出现一次的字母。如果出现重复了,就从队列中找到下一个无重复的数。

class Solution{
public:
    Solution()
    {
        memset(cnt, 0, sizeof cnt);
    }
    int cnt[256];
    queue<char> q;
    
    //Insert one char from stringstream
    void insert(char ch){
        if(++cnt[ch] > 1)
        {
            while(q.size() && cnt[q.front()] > 1) q.pop();
        }
        else q.push(ch);
    }
    //return the first appearence once char in current stringstream
    char firstAppearingOnce(){
        if(q.empty()) return '#';
        return q.front();
    }
};

65. 数组中的逆序对

在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。

输入一个数组,求出这个数组中的逆序对的总数。

样例
输入:[1,2,3,4,5,6,0]

输出:6

归并排序。合并a,b两个序列。如果a[i] > b[j]。说明a[i]以及以后所有的数都要比b[j]大,都是逆序对,加上这些个数就好。

class Solution {
public:
    int tmp[100000];
    int inversePairs(vector<int>& nums) {
        return merge(nums, 0, nums.size() - 1);
    }
    
    int merge(vector<int>& nums, int l, int r)
    {
        if(l >= r) return 0;
        int mid = l + r >> 1;
        int cnt = merge(nums, l, mid) + merge(nums, mid + 1, r);
        int i = l, j = mid + 1, id = 0;
        
        while(i <= mid && j <= r)
        {
            if(nums[i] <= nums[j]) tmp[id++] = nums[i++];
            else cnt += mid + 1 - i, tmp[id++] = nums[j++];
        }
        while(i <= mid) tmp[id++] = nums[i++];
        while(j <= r) tmp[id++] = nums[j++];
        
        for(int i = l, id = 0; i <= r; i++, id++) nums[i] = tmp[id];
        return cnt;
    }
};

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

输入两个链表,找出它们的第一个公共结点。

当不存在公共节点时,返回空节点。

样例
给出两个链表如下所示:
A: a1 → a2

c1 → c2 → c3

B: b1 → b2 → b3

输出第一个公共节点c1

先计算从两个头开始的长度。然后多的那边多走几步就好。然后一起走,走到一起的时候就说明是公共节点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
        int n1 = 0, n2 = 0;
        auto p1 = headA, p2 = headB;
        while(p1)
        {
            n1++;
            p1 = p1->next;
        }
        while(p2)
        {
            n2++;
            p2 = p2->next;
        }
        auto fast = headA, slow = headB;
        if(n2 > n1) swap(fast, slow);
        int diff = abs(n1 - n2);
        while(diff-- && fast) fast = fast->next;
        while(fast && slow && fast != slow)
        {
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
    }
};

67. 数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。

例如输入排序数组[1, 2, 3, 3, 3, 3, 4, 5]和数字3,由于3在这个数组中出现了4次,因此输出4。

样例
输入:[1, 2, 3, 3, 3, 3, 4, 5] , 3

输出:4

二分出上下边界。然后输出长度就好。

class Solution {
public:
    int getNumberOfK(vector<int>& nums , int k) {
        int n = nums.size();
        if(!n) return 0;
        int l = 0, r = n - 1;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(nums[mid] >= k) r = mid;
            else l = mid + 1;
        }
        int left = l;
        if(nums[l] != k) return 0;
        l = 0, r = n - 1;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(nums[mid] <= k) l = mid;
            else r = mid - 1;
        }
        int right = l;
        return right - left + 1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值