剑指offer

这篇博客详细介绍了多个AcWing和剑指Offer的算法题目,包括数组中重复数字的查找、二维数组操作、链表操作、树形结构问题、斐波那契数列等,旨在提升编程能力。
摘要由CSDN通过智能技术生成

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

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n = nums.size();
        for (auto x : nums)
        //首先遍历数组,如果存在某个数不在0到n-1的范围内,则返回-1,防止下标越界
            if (x < 0 || x >= n)
                return -1;
        //把每个数放到对应的位置上,即让 nums[i] = i
        for (int i = 0; i < nums.size(); i ++ ) {
            while (nums[i] != i) {
                if (nums[i] == nums[nums[i]])
                    return nums[i];
                swap(nums[i], nums[nums[i]]);
//每次交换都会将一个数放在正确的位置上,所以swap操作最多会进行 n 次,不会发生死循环
            }
        }

        return -1;
    }
};

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

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
/*
要求不修改数组

抽屉原理+分治

抽屉原理:n+1 个苹果放在 n 个抽屉里,那么至少有一个抽屉中会放两个苹果。
用在这个题目中就是,一共有 n+1 个数,每个数的取值范围是1到n,所以至少会有一个数出现两次。
注意这里与上一道题不一样,上道题数据范围0-n-1,n个数,就是n个苹果,n个抽屉,不能用抽屉原理

采用分治的思想,将每个数的取值的区间[1, n]划分成[1, n/2]和[n/2+1, n]两个子区间,
然后分别统计两个区间中数的个数。
注意这里的区间是指数的取值范围,而不是数组下标。
每次我们可以把区间长度缩小一半,直到区间长度为1

时间复杂度:每次会将区间长度缩小一半,一共会缩小O(logn) 次。
每次统计两个子区间中的数时需要遍历整个数组,时间复杂度是O(n)。
所以总时间复杂度是O(nlogn)。

空间复杂度:代码中没有用到额外的数组,所以额外的空间复杂度是O(1)。
*/
        int l = 1, r = nums.size() - 1;
        while (l < r) {
            int mid = l + r >> 1; // 划分的区间:[l, mid], [mid + 1, r]
            int s = 0;
            for (auto x : nums) s += x >= mid + 1 && x <= r;
            if (s > r - mid) l = mid + 1;
            else r = mid;
        }
        return r;
    }
};
class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
/*
要求不修改数组

(模拟链表) O(n)
两点前置知识:
1. 如何判断链表是否存在环?
双指针,一快(每次跑两格)一慢(每次跑一格),从链表首部开始遍历
两个指针最终都会进入环内,由于快指针每次比慢指针多走一格,
因此快指针一定能在环内追上慢指针。而如果链表没环,那么快慢指针不会相遇。
2. 对于有环的链表,如何找到环的起点?
基于第一点,快慢指针相遇时,我们可以证明相遇的点与环起点的距离,
一定和链表首部与环起点的距离相等。

我们可以将数组视为一个(或多个链表),每个元素都是一个节点,元素的下标代表节点地址,
元素的值代表next指针,因此,重复的元素意味着两个节点的next指针一样,
即指向同一个节点,因此存在环,且环的起点即重复的元素。

为了找到任意一个环的起点(重复元素),我们只需要拿到一个链表的首部,
然后利用前置知识即可解决问题。显然,0一定是一个链表的首部,
因为所有元素值的范围在1 - n-1之间,即没有节点指向0节点。

题解流程即为:从0开始,快慢指针分别以2、1的速度向前遍历,当它们相遇时,
将快指针置为0,继续分别以1、1的速度向前遍历,当它们再次相遇时,此时它们的下标就是题解。

时间复杂度分析:慢指针每次走一格,刚好遍历到链表尾部(即环起点)处结束,因此复杂度为O(n)
空间复杂度分析:O(1)

*/
        int a = 0, b = 0;//都从0开始走
        while (true) {
            a = nums[a];//这里不会存在走到空节点的情况,直接走就行
            b = nums[nums[b]];
            if (a == b) {
                a = 0;
                while (a != b) {
                    a = nums[a];
                    b = nums[b];
                }
                return a;
            }
        }

        return -1;
    }
};

AcWing 15. 二维数组中的查找

class Solution {
public:
    bool searchArray(vector<vector<int>> array, int target) {
/*
(单调性扫描) O(n+m)
核心在于发现每个子矩阵右上角的数的性质:
因此我们可以从整个矩阵的右上角开始枚举,假设当前枚举的数是x:

如果 x 等于target,则说明我们找到了目标值,返回true;
如果 x 小于target,则 x 左边的数一定都小于target,我们可以直接排除当前一整行的数;
如果 x 大于target,则 x 下边的数一定都大于target,我们可以直接排序当前一整列的数;
排除一整行就是让枚举的点的横坐标加一,排除一整列就是让纵坐标减一。
当我们排除完整个矩阵后仍没有找到目标值时,就说明目标值不存在,返回false。

时间复杂度分析
每一步会排除一行或者一列,矩阵一共有 n 行,m 列,所以最多会进行 n+m 步。
所以时间复杂度是 O(n+m)。
*/
        if (array.empty() || array[0].empty()) return false;
        int i = 0, j = array[0].size() - 1;
        while (i <= array.size()-1 && j >= 0) {
            if (array[i][j] == target) return true;
            if (array[i][j] > target) j -- ;
            else i ++ ;
        }
        return false;
    }
};

AcWing 16. 替换空格

class Solution {
public:
    string replaceSpaces(string &str) {
        //线性扫描
        string res;
        for (auto c : str)
            if (c == ' ' ) res += "%20";
            else res += c;
        return res;        
    }
};
class Solution {
public:
    string replaceSpaces(string &str) {
/*
不能用string,需要自己malloc出char数组来存储答案。
(双指针扫描) O(n)
动态地将原数组长度扩大,此时我们就可以使用双指针算法,来降低空间的使用:

首先遍历一遍原数组,求出最终答案的长度length;
将原数组resize成length大小;
使用两个指针,指针i指向原字符串的末尾,指针j指向length的位置;
两个指针分别从后往前遍历,如果str[i] == ' ',则指针j的位置上依次填充'0', '2', '%',
这样倒着看就是"%20";如果str[i] != ' ',则指针j的位置上填充该字符即可。
由于i之前的字符串,在变换之后,长度一定不小于原字符串,所以遍历过程中一定有i <= j,
这样可以保证str[j]不会覆盖还未遍历过的str[i],从而答案是正确的。
*/
        int len = 0;
        for (auto c : str)
            if (c == ' ')
                len += 3;
            else
                len ++ ;

        int i = str.size() - 1, j = len - 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;
    }
};

AcWing 17. 从尾到头打印链表

/**
 * 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> res;
        for (auto p = head; p; p = p->next) res.push_back(p->val);
        reverse(res.begin(), res.end());
        return res;        
    }
};

AcWing 18. 重建二叉树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
/*
先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
在中序遍历中找到根节点的位置 k,则 k 左边是左子树的中序遍历,右边是右子树的中序遍历;
假设左子树的中序遍历的长度是 l,则在前序遍历中,根节点后面的 l 个数,是左子树的前序遍历,
剩下的数是右子树的前序遍历;
有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点;

我们在初始化时,用哈希表(unordered_map<int,int>)记录每个值在中序遍历中的位置,
n个节点,递归n次,在递归到每个节点时,在中序遍历中查找根节点位置的操作需要 O(1) 的时间
此时,创建每个节点需要的时间是 O(1),,
所以总时间复杂度是 O(n)
*/
class Solution {
public:
    //哈希表记录每个值在中序遍历中的位置
    unordered_map<int,int> pos;

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        for (int i = 0; i < n; i ++ )
            pos[inorder[i]] = i;
        return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
    }

    TreeNode* dfs(vector<int>&pre, vector<int>&in, int pl, int pr, int il, int ir)
    {
        if (pl > pr) return NULL;
        int k = pos[pre[pl]] - il;//找到左子树的节点个数
        auto root = new TreeNode(pre[pl]);
        root->left = dfs(pre, in, pl + 1, pl + k, il, il + k - 1);
        root->right = dfs(pre, in, pl + k + 1, pr, il + k + 1, ir);
        return root;
    }
};

AcWing 19. 二叉树的下一个节点

在这里插入图片描述

/**
 * 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) {
/*
(模拟) O(h)
这道题目就是让我们求二叉树中给定节点的后继。

分情况讨论即可,如图所示:

如果当前节点有右儿子,则右子树中最左侧的节点就是当前节点的后继。比如F的后继是H;
如果当前节点没有右儿子,则需要沿着father域一直向上找,找到第一个是其father左儿子的节点,
该节点的father就是当前节点的后继。比如当前节点是D,则第一个满足是其father左儿子的节点是F,
则C的father就是D的后继,即F是D的后继。

时间复杂度分析
不论往上找还是往下找,总共遍历的节点数都不大于树的高度。所以时间复杂度是 O(h),
其中 h 是树的高度。
*/
        if (!p) return NULL;
        if (p->right) {
            p = p->right;
            while (p->left) p = p->left;
            return p;
        }

        while (p->father && p->father->right == p) {
            p = p->father;
        }

        if (p->father) return p->father;
        return NULL;
    }
};

AcWing 20. 用两个栈实现队列

class CQueue {
public:
/*
维护两个栈,第一个栈支持插入操作,第二个栈支持删除操作
*/
    stack<int> stk1,stk2;
    CQueue() {
        while(stk1.size()) stk1.pop();
        while(stk2.size()) stk2.pop();
    }
    
    void appendTail(int value) {
        stk1.push(value);
    }
    
    int deleteHead() {
        if(stk2.empty()){
            while(stk1.size()){
                stk2.push(stk1.top());
                stk1.pop();
            }
        }

        if(stk2.empty()) return -1;
        else{
            int q=stk2.top();
            stk2.pop();
            return q;
        }
    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */
/*
这是一道基础题,只要把功能实现对就可以,不需要考虑运行效率。

我们用两个栈来做,一个主栈,用来存储数据;一个辅助栈,用来当缓存。

push(x),我们直接将x插入主栈中即可。
pop(),此时我们需要弹出最先进入栈的元素,也就是栈底元素。
我们可以先将所有元素从主栈中弹出,压入辅助栈中。则辅助栈的栈顶元素就是我们要弹出的元素,
将其弹出即可。然后再将辅助栈中的元素全部弹出,压入主栈中。
peek(),可以用和pop()操作类似的方式,得到最先压入栈的元素。
empty(),直接判断主栈是否为空即可。

时间复杂度分析
push():O(1);
pop(): 每次需要将主栈元素全部弹出,再压入,所以需要 O(n) 的时间;
peek():类似于pop(),需要 O(n) 的时间;
empty():O(1);
*/
class MyQueue {
public:
    /** Initialize your data structure here. */
    stack<int> stk, cache;
    MyQueue() {

    }

    /** Push element x to the back of queue. */
    void push(int x) {
        stk.push(x);
    }

    void copy(stack<int> &a, stack<int> &b) {
        while (a.size()) {
            b.push(a.top());
            a.pop();
        }
    }

    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        copy(stk, cache);
        int res = cache.top();
        cache.pop();
        copy(cache, stk);
        return res;
    }

    /** Get the front element. */
    int peek() {
        copy(stk, cache);
        int res = cache.top();
        copy(cache, stk);
        return res;
    }

    /** Returns whether the queue is empty. */
    bool empty() {
        return stk.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();
 */

AcWing 21. 斐波那契数列

class Solution {
public:
    int fib(int n) {
        int a = 0, b = 1;
        if(n==0) return a;
        if(n==1) return b;
        
        for (int i = 2; i <= n; i++){
            int c=(a+b)%1000000007;
            a = b;
            b = c;
        }

        return b;
        // int a=0,b=1;
        // while(n--){
        //     int c=(a+b)%1000000007;
        //     a=b;
        //     b=c;
        // }
        // return a;
    }
};

在这里插入图片描述

/*
矩阵快速幂,O(log n)
*/
class Solution {
    typedef long long ll;
    const int MOD=1e9+7;
    ll a[2][2]{{1,0},{0,1}}, b[2][2]{{1,1},{1,0}}; // a for answer, b for base
public:
    int fib(int n) {
        while(n){
            if(n&1) ab();
            bb();
            n>>=1;
        }
        return a[0][1];
    }
    
    void ab(){  // a*b
        ll t11=a[0][0], t12=a[0][1], t21=a[1][0], t22=a[1][1];
        a[0][0]=(t11*b[0][0] + t12*b[1][0])%MOD;
        a[0][1]=(t11*b[1][0] + t12*b[1][1])%MOD;
        a[1][0]=a[0][1];      // a、b均是斐波那契矩阵中的某一个,所以是对称矩阵
        a[1][1]=(t21*b[1][0] + t22*b[1][1])%MOD;
    }
    
    void bb(){  // b*b
        ll t11=b[0][0], t12=b[0][1], t21=b[1][0], t22=b[1][1];
        b[0][0]=(t11*t11 + t12*t21)%MOD;
        b[0][1]=(t11*t12 + t12*t22)%MOD;
        b[1][0]=b[0][1];
        b[1][1]=(t21*t12 + t22*t22)%MOD;
    }
};

剑指 Offer 10- II 青蛙跳台阶问题

/*
青蛙跳台阶问题: f(0)=1 , f(1)=1, f(2)=2;
斐波那契数列问题: f(0)=0, f(1)=1, f(2)=1 。
*/
class Solution {
public:
    int numWays(int n) {
        int a=1,b=1;
        while(n--){
            int c=(a+b)%1000000007;
            a=b;
            b=c;
        }
        return a;
        // int a = 1, b = 1;

        // for (int i = 2; i <= n; i++){
        //     int c=(a+b)%1000000007;
        //     a = b;
        //     b = c;
        // }

        // return b;
    }
};

AcWing 22. 旋转数组的最小数字

在这里插入图片描述

/*
(二分) O(n)O(n)
为了便于分析,我们先将数组中的数画在二维坐标系中,横坐标表示数组下标,纵坐标表示数值,
图中水平的实线段表示相同元素。

我们发现除了最后水平的一段(黑色水平那段)之外,其余部分满足二分性质:竖直虚线左边的数满足 
nums[i]≥nums[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--;
//跳出循环可能为三种情况,n越来越小,小到等于0,也就是nums[n]=nums[0];
//nums[n]>nums[0];nums[n]<nums[0]。
//其中前两种需要排除,最后一种情况需要继续探索
        if(n==0||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]) r=mid;
            else l=mid+1;
        }
        return nums[l];
    }
};

AcWing 23. 矩阵中的路径

/*
(DFS) O(n^2*3^k)
在深度优先搜索中,最重要的就是考虑好搜索顺序。

我们先枚举单词的起点,然后依次枚举单词的每个字母。
过程中需要将已经使用过的字母改成一个特殊字母,以避免重复使用字符。

时间复杂度分析:单词起点一共有 n^2 个,单词的每个字母一共有上下左右四个方向可以选择,
但由于不能走回头路,所以除了单词首字母外,仅有三种选择。所以总时间复杂度是 O(n^2*3^k)
*/
class Solution {
public:
    bool hasPath(vector<vector<char>>& matrix, string &str) {
        for(int i=0;i<matrix.size();i++){
            for(int j=0;j<matrix[i].size();j++){
                if(dfs(matrix,str,0,i,j)) return true;
            }
        }
        return false;
    }
    
    bool dfs(vector<vector<char>>&matrix,string& str, int u, int x, int y){
        if(matrix[x][y]!=str[u]) return false;
        if(u==str.size()-1) return true;
        int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
        char t=matrix[x][y];
        matrix[x][y]='*';
        for(int i=0;i<4;i++){
            int a=x+dx[i],b=y+dy[i];
            if(a>=0&&a<matrix.size()&&b>=0&&b<matrix[a].size()){
                if(dfs(matrix,str,u+1,a,b)) return true;
            }
        }
        matrix[x][y]=t;
        return false;
    }
    
};

AcWing 24. 机器人的运动范围

/*
这是一个典型的宽度优先搜索问题,我们从 (0, 0) 点开始,
每次朝上下左右四个方向扩展新的节点即可。dfs在数据较大时,可能会栈溢出
扩展时需要注意新的节点需要满足如下条件:

之前没有遍历过,这个可以用个bool数组来判断;
没有走出边界;
横纵坐标的各位数字之和小于 k;
最坏情况下会遍历方格中的所有点,所以时间复杂度就是 O(nm)
*/
class Solution {
public:

    int get_sum(pair<int, int> p) {//计算每个坐标的数位之和
        int s = 0;
        while (p.first) {
            s += p.first % 10;
            p.first /= 10;
        }
        while (p.second) {
            s += p.second % 10;
            p.second /= 10;
        }
        return s;
    }

    int movingCount(int threshold, int rows, int cols)
    {
        if (!rows || !cols) return 0;//特判边界
        queue<pair<int,int>> q;//定义宽搜队列,存放坐标
        vector<vector<bool>> st(rows, vector<bool>(cols, false));
        //判重数组,其实vector会默认初始化为false

        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

        int res = 0;
        q.push({0, 0});//最开始先把起始点入队
        while (q.size()) {
            auto t = q.front();
            q.pop();
            //判断当前点是否合法
            if (st[t.first][t.second] || get_sum(t) > threshold) continue;
            res ++ ;
            st[t.first][t.second] = true;//标记一下
            for (int i = 0; i < 4; i ++ ) {
                int x = t.first + dx[i], y = t.second + dy[i];
                if (x >= 0 && x < rows && y >= 0 && y < cols) q.push({x, y});
            }
        }

        return res;
    }
};

AcWing 25. 剪绳子

/*
选用尽量多的3,直到剩下2或者4时,用2。
时间复杂度分析:
当n比较大时,n会被拆分成 ⌈n/3⌉ 个数,我们需要计算这么多次减法和乘法,所以时间复杂度是 O(n)
*/
class Solution {
public:
    int maxProductAfterCutting(int n) {
        if(n<=3) return n-1;
        int res=1;
        if(n%3==1) n-=4,res*=4;
        else if(n%3==2) n-=2,res*=2;
        while(n) n-=3,res*=3;
        return res;   
    }
};
/*
设置一个数组dp[n+1],dp[ i ]存储绳子长度为i 时的最大乘积。依题意,绳子至少被剪一次,
绳子长度最小为2。外层for循环从绳长为i=2的情况开始依次计算,直到计算到绳长为n的情况。
内层for循环:当绳长为i时,由于已知至少剪一刀,我们索性假设第一刀剪在长度为j的位置
(即第一段绳子长度为j)。剩下的那段长度为( i - j )的绳子就变成了“可剪可不剪”。
那究竟是“不剪了”得到的乘积大呢,还是“继续剪余下的这段”得到乘积更大?
我们不知道,所以需要两种情况都计算一下进行比较。
其中,“不剪了”得到的乘积是j * ( i - j ),
“继续剪”得到的乘积是j * dp[ i - j ]。
取其中的较大值,就是“第一剪在j位置”能得到的最大乘积。
而第一剪的所有可能位置是1,2,…,i-1。依次计算所有可能情况,取最大值即为dp[ i ]的值。
*/
class Solution {
public:
    int maxProductAfterCutting(int n) {
        vector<int> f(n+1,0);
        for(int i=2;i<=n;i++){
            for(int j=1;j<i;j++){
                f[i]=max(f[i],max( j*(i-j) , j*f[i-j]) );
            }
        }
        return f[n];
    }
};

剑指 Offer 14- II 剪绳子 II

class Solution {
public:
    int cuttingRope(int n) {
        if(n<=3) return n-1;
        long long res=1;
        if(n%3==1) n-=4,res*=4;
        else if(n%3==2) n-=2,res*=2;
        while(n) n-=3,(res*=3)%=1000000007;
        return res;   
    }
};
class Solution {
public:
    int cuttingRope(int n) {
        if(n<=3) return n-1;
        int a = n/3, b = n%3;
        int mod = 1e9+7;
        if(b==0) return pow1(3,a,mod,1);
        if(b==1) return pow1(3,a-1,mod,4);
        return pow1(3,a,mod,2);
    }

    long long pow1(long long a,long long n,long long mod,int mul){
        long long res = 1;
        while(n){
            if(n&1) res = (res * a)%mod;
            a = (a * a)%mod;
            n >>= 1;
        }
        res = (res*mul)%mod;
        return res;
    }
};

AcWing 26. 二进制中1的个数

/*
有个难点是如何处理负数。
在C++中如果我们右移一个负整数,系统会自动在最高位补1,这样会导致 n 永远不为0,
就死循环了。
解决办法是把 n 强制转化成无符号整型,这样 n 的二进制表示不会发生改变,
但在右移时系统会自动在最高位补0。
*/
class Solution {
public:
    int NumberOf1(int n) {
        int res = 0;
        for (int i = 0; i < 32; i ++ )
            if (n >> i & 1)
            //这里不用强转成unsigned int 是因为我这里只是查看倒数第i位,前面不关心
                res ++ ;
        return res;        
    }
};
class Solution {
public:
    int NumberOf1(int n) {
        int res = 0;

        while (n) n -= n & -n, res ++ ;

        return res;      
    }
};
class Solution {
public:
    int NumberOf1(int n) {
        int res = 0;
        unsigned int un = n; 
        while (un) res += un & 1, un >>= 1;//un&1得到un的最后一位
        return res;    
    }
};

AcWing 27. 数值的整数次方

class Solution {
public:
    typedef long long LL;
//由于本题的指数是int范围,可能很大,所以需要用快速幂求解   
    double Power(double x, int n) {
        bool is_minus = n < 0;
        double res = 1;
        LL k = abs(LL(n));
        while(k){
            if (k & 1) res *= x;
            x *= x;
            k>>=1;
        }
        if (is_minus) res = 1 / res;
        return res;        
    }
};

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

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

        // node->val = node->next->val;
        // node->next = node->next->next;
    }
};

剑指 Offer 18 删除链表的节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(!head) return head;

        auto dummy=new ListNode(-1),p=dummy;
        dummy->next=head;

        while(p->next && p->next->val != val){
            p=p->next;
        }

        if(p->next && p->next->val == val)  p->next = p->next->next;
        
        return dummy->next;
    }
};

剑指 Offer 17 打印从1到最大的n位数

class Solution {
public:
//不考虑n较大的情况
    vector<int> printNumbers(int n) {
        vector<int> res;
        int m = 1;
        while(n--)  m *= 10;
        for (int i = 1; i < m; i++)
        {
            res.push_back(i);
        }
        return res;
    }
};
class Solution
{
public:
    vector<int> printNumbers(int n)
    {
        vector<int> nums;

        string num(n, '0');
        while (!Increase(num)) //直到当前的string数字自增发生越界,才退出循环
        {
            Save(nums, num);
        }
        return nums;
    }

    /*实现string数字自增操作*/
    bool Increase(string& num)//注意传引用
    {
        bool IsOverflow = false; //越界判断标记
        int carry = 0; //进位标记
        int n = num.size();
        for (int i = n - 1; i >= 0; i--)
        {
            int digit = num[i] - '0' + carry; //存储第i位数的int形式
            if (i == n - 1) //如果是个位,则需要自增
            {
                digit++;
            }
            if (digit >= 10) //如果当前位数超过10,则需要进位
            {
                if (i == 0) //如果在最高位进位,则已经越界
                {
                    IsOverflow = true;
                }
                else //如果在非最高位进位,则设置进位标记并记录当前位数
                {
                    carry = 1;
                    num[i] = digit - 10 + '0';
                }
            }
            else //如果未发生进位,则记录当前位数后可结束循环
            {
                num[i] = digit + '0';
                break;
            }
        }
        return IsOverflow;
    }

    /*实现字符串数字去掉高位0并转换为int存入nums向量操作*/
    void Save(vector<int>& nums, string num)
    {
        string temp_s = "";
        bool IsBeginZero = true; //高位0标记
        for (int i = 0; i < num.size(); i++)
        {//去掉前面的一堆字符0
            if (IsBeginZero && num[i] != '0')
            {
                IsBeginZero = false;
            }
            if (!IsBeginZero)
            {
                temp_s += num[i];
            }
        }
        int temp_i = stoi(temp_s);
        nums.push_back(temp_i);
    }
};
class Solution
{
public:
    vector<int> nums;
    vector<int> printNumbers(int n)
    {
        
        string num(n, '0');
        dfs(num,  0);
        return nums;
    }

    /*递归实现从最高位到最低位的数字全排列*/
    void dfs(string& num, int index)
    {
        if (index == num.size()) //如果索引index指向最低位的右侧,则到达递归边界,保存当前数字后返回
        {
            Save(num);
            return;
        }
        else
        {
            for (int i = 0; i <= 9; i++) //每一位数从0到9排列,记录当前位数的一种情况后递归进行下一位数的排列
            {
                num[index] = '0' + i;
                dfs(num, index + 1);
            }
        }
    }

    /*实现字符串数字去掉高位0并转换为int存入nums向量操作*/
    void Save(string num)
    {
        string temp_s = "";
        bool IsBeginZero = true; //高位0标记
        for (int i = 0; i < num.size(); i++)
        {
            if (IsBeginZero && num[i] != '0')
            {
                IsBeginZero = false;
            }
            if (!IsBeginZero)
            {
                temp_s += num[i];
            }
        }
        if (temp_s != "") //注意全排列递归解法在排列时会产生全0如"00000",导致temp_s为空,此时不能转换为整数
        {
            int temp_i = stoi(temp_s);
            nums.push_back(temp_i);
        }
    }
};

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
/*
为了方便处理边界情况,我们定义一个虚拟元素 dummy指向链表头节点。
然后从前往后扫描整个链表,每次扫描元素相同的一段,如果这段中的元素个数多于1个,
则将整段元素直接删除。
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* head) {
        auto dummy = new ListNode(-1);
        dummy->next = head;

        auto p = dummy;
        while (p->next) {//判断p->next这个确认存在的节点是否重复,重复则全删
            auto q = p->next;
            //q从p->next这个节点一直往后,直到q->val与待判断的节点不相同为止
            while (q && p->next->val == q->val) q = q->next;
            //如果跳出循环时,q存在,则q是待判断的节点p->next后面第一个不同的节点
            if (p->next->next == q) p = p->next;
            else p->next = q;//继续判断
        }

        return dummy->next;
    }
};

AcWing 30. 正则表达式匹配

class Solution {
public:
/*
类似dp问题很多,比如给两个字符串,求最长公共子序列,能否匹配
正则表达式匹配,'.' 匹配任意单个字符,'*' 表示它前面的那一个字符可以出现任意多次(包括0次)
动态规划(序列模型):
状态表示 f[i,j]
    集合:所有s[1-i]和p[1-j]的匹配方案
    属性:bool 是否存在合法方案
状态计算
    如果p[j]不是'*',先看s[i]和p[j]是否匹配,两种情况:s[i]==p[j]或者p[j]=='.',并且f[i-1,j-1]也匹配
    如果p[j]是'*',枚举一下,这个'*'表示多少个字符,如果是0个字符f[i,j-2],1个字符f[i-1,j-2]&&s[i]匹配
    2个字符f[i-2,j-2]&&s[i]匹配&&s[i-1]匹配...
    f[i,j]  =f[i,j-2]   | f[i-1,j-2]&s[i]   | f[i-2,j-2]&s[i]&s[i-1]...
    f[i-1,j]=f[i-1,j-2] | f[i-2,j-2]&s[i-1] | f[i-3,j-2]s[i-1]&s[i-2]...
    上面一个式子: f[i,j]  =f[i,j-2] |  f[i-1,j] & s[i]匹配 优化和完全背包很像
*/
    bool isMatch(string s, string p) {
        int n = s.size(), m = p.size();
        s = ' ' + s, p = ' ' + p;//下标都从1开始,所以前面补上一个空格
        vector<vector<bool>> f(n + 1, vector<bool>(m + 1));//布尔数组
        f[0][0] = true;//初始化
        for (int i = 0; i <= n; i ++ )//它可以从0开始,因为没有字符的时候也可能匹配
            for (int j = 1; j <= m; j ++ ) {
//如果j==0的话,因为f[0][0]已经初始化过了,其他i不为0的情况一定不匹配,所以j从0开始没有意义
            //*和前面一个字符看作一个整体,如果遇到类似a*的a的话要跳过a
                if (j + 1 <= m && p[j + 1] == '*') continue;
                if ( p[j] != '*') {//如果i指向某个非空字符,并且p[j]!='*',i从1开始,否则i-1没有意义
                    f[i][j] = i &&f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '.');
                } else if (p[j] == '*') {
                    f[i][j] = f[i][j - 2] || i && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.');
                }
            }

        return f[n][m];        
    }
};

AcWing 31. 表示数值的字符串

class Solution {
public:
    bool isNumber(string s) {

        int i = 0, j = s.size() - 1;//滤除前后的空格,指针后移
        while(s[i] == ' ') ++i;
        // cout<<i<<endl;
        // cout<<s[i]<<endl; 当s==" "时,可以输出s[1],但不能输出s[-1];所以while循环要加j>=0
        while (j >= 0 && s[j] == ' ') j -- ;
        cout<<s[j]<<endl;
        // if (i > j) return false;
        s = s.substr(i, j - i + 1);

        bool isNum = false; 
        //该变量表示从位置0开始,到i位置的字符串是否构成合法数字,初始化为false
        i=0;
        if(s[i] == '+' || s[i] == '-') ++i; //一个‘-’或‘+’为合法输入,指针后移

        while(s[i] >= '0' && s[i] <= '9'){  //此处如果出现数字,为合法输入,指针后移,同时isNum置为true
            isNum = true;  //显然,在此处,前面的所有字符是可以构成合法数字的
            ++i;
        }
//按照前面的顺序,在此处出现小数点也是合法的,指针后移(此处能否构成合法字符取决于isNum)
        if(s[i] == '.') ++i;    

        while(s[i] >= '0' && s[i] <= '9'){  //小数点后出现数字也是合法的,指针后移
            isNum = true;   //无论前面是什么,此处应当是合法数字
            ++i;
        }

//上面的部分已经把所有只包含小数点和正负号以及数字的情况包括进去了,如果只判断不含E或e的合法数字,到此处就可以停止了

//当前面的数字组成一个合法数字时(isNum = true),此处出现e或E也是合法的
        if(isNum && (s[i] == 'e' || s[i] == 'E')){ 
            ++i;
            isNum = false; //但到此处,E后面还没有数字,此时的isNum应等于false;

            if(s[i] == '-' || s[i] == '+') ++i; //E或e后面可以出现一个‘-’或‘+’,指针后移

            while(s[i] >= '0' & s[i] <= '9') {
                ++i;
                isNum = true; //E后面接上数字后也就成了合法数字
            }
        }

        //如果字符串为合法数字,指针应当移到最后,即是s[i] == '\0' 同时根据isNum判断数字是否合法
        //整个过程中只有当i位置处的输入合法时,指针才会移动
        return (i == s.size() && isNum);
    }
};

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

/*
用两个指针分别从首尾开始,往中间扫描。扫描时保证第一个指针前面的数都是奇数,第二个指针后面的数都是偶数。

每次迭代时需要进行的操作:

第一个指针一直往后走,直到遇到第一个偶数为止;
第二个指针一直往前走,直到遇到第一个奇数为止;
交换两个指针指向的位置上的数,再进入下一层迭代,直到两个指针相遇为止;
*/
class Solution {
public:
    void reOrderArray(vector<int> &array) {
         int i = 0, j = array.size() - 1;
         while (i < j)
         {
             while (i < j && array[i] % 2) i ++ ;
             while (i < j && array[j] % 2 == 0) j -- ;
             if (i < j) swap(array[i], array[j]);
         }         
    }
};

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
//一次循环
        auto dummy = new ListNode(-1);  // 首先新建一个链表的结点
        dummy->next = head;                     //令这个结点指向head
        auto first = dummy, second = dummy;   //同时设置双指针指向该节点
        for (int i = 0; i < k; i ++ )   
        //将first指针向前走k步,second 指针不动,还是在最前面,此时first和second这个节点相差k
        {
            first = first->next;
        }

            while (first -> next)
        {
//始终保持两个指针之间间隔n个结点,在first到达终点时,second的下一个结点就是从结尾数第n个结点
            first = first->next;
            second = second->next;
        }

        return second->next; 
    }
};

AcWing 34. 链表中环的入口结点

/**
 * 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) {
        if (!head) return 0;
        auto first = head, second = head;

        while (first && second)
        {
            first = first->next;
            second = second->next;
            if (second) second = second->next;
            else return 0;

            if (first == second)
            {
                first = head;
                while (first != second)
                {
                    first = first->next;
                    second = second->next;
                }
                return first;
            }
        }

        return 0;
    }
};

AcWing 35. 反转链表

/**
 * 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) {
        if (!head || !head->next) return head;

        auto p = head, q = p->next;
        while (q)
        {
            auto o = q->next;
            q->next = p;
            p = q, q = o;
        }
        head->next = NULL;

        return p;
    }
}

AcWing 36. 合并两个排序的链表

/**
 * 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) {
        auto dummy = new ListNode(-1), tail = dummy;
        while (l1 && l2)
            if (l1->val < l2->val)
            {
                tail = tail->next = l1;
                l1 = l1->next;
            }
            else
            {
                tail = tail->next = l2;
                l2 = l2->next;
            }

        if (l1) tail->next = l1;
        if (l2) tail->next = l2;

        return dummy->next;
    }  
};

AcWing 37. 树的子结构

/**
 * 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:
/*
这道题就是把字符串匹配扩展到树上面
遍历树A中的所有非空节点R;
判断树A中以R为根节点的子树是不是包含和树B一样的结构,且我们从根节点开始匹配;
对于第一部分,我们直接递归遍历树A即可,遇到非空节点后,就进行第二部分的判断。

对于第二部分,我们同时从根节点开始遍历两棵子树:

如果树B中的节点为空,则表示当前分支是匹配的,返回true;
如果树A中的节点为空,但树B中的节点不为空,则说明不匹配,返回false;
如果两个节点都不为空,但数值不同,则说明不匹配,返回false;
否则说明当前这个点是匹配的,然后递归判断左子树和右子树是否分别匹配即可;
最坏情况下,我们对于树A中的每个节点都要递归判断一遍,
每次判断在最坏情况下需要遍历完树B中的所有节点。
所以时间复杂度是 O(nm),其中 n 是树A中的节点数, m 是树B中的节点数。
*/
    bool hasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        if (!pRoot1 || !pRoot2) return false;
        if (isSame(pRoot1, pRoot2)) return true;
        return hasSubtree(pRoot1->left, pRoot2) || hasSubtree(pRoot1->right, pRoot2);
    }
    //从节点pRoot1开始是否完全匹配以pRoot2为根节点的子树
    bool isSame(TreeNode* pRoot1, TreeNode* pRoot2) {
        if (!pRoot2) return true;//说明在之前的匹配过程中已经完全匹配
        if (!pRoot1 || pRoot1->val != pRoot2->val) return false;
        return isSame(pRoot1->left, pRoot2->left) && isSame(pRoot1->right, pRoot2->right);
    }
};

AcWing 38. 二叉树的镜像

/**
 * 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:
/*
镜像后的树就是将原树的所有节点的左右儿子互换
所以我们递归遍历原树的所有节点,将每个节点的左右儿子互换即可
原树仅被遍历一次,所以时间复杂度是O(n)
*/
    void mirror(TreeNode* root) {
        if (root==NULL) return;
        swap(root->left, root->right);
        mirror(root->left);
        mirror(root->right);        
    }
};
//注意与leetcode的返回不同
    TreeNode* mirrorTree(TreeNode* root) {
        if (root==NULL) return root;
        swap(root->left,root->right);
        root->left=mirrorTree(root->left);
        root->right=mirrorTree(root->right);
        return root;
    }

AcWing 39. 对称的二叉树

/**
 * 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:
/*
(二叉树,递归) O(n)
递归判断两个子树是否互为镜像
两个子树互为镜像当且仅当:
两个子树的根节点值相等
第一棵子树的左子树和第二棵子树的右子树互为镜像,且第一棵子树的右子树和第二棵子树的
左子树互为镜像
从上到下每个节点仅被遍历一遍,所以时间复杂度是 O(n)
*/
    bool isSymmetric(TreeNode* root) {
        if(!root) return true;
        return dfs(root->left,root->right);

    }
    bool dfs(TreeNode* l,TreeNode* r){
        if(!l||!r) return !l&&!r;//如果左节点为空或者右节点为空,两者必须为空才可能相等
        if(l->val != r->val) return false;//如果左边右边的值不相等,则不等
        return dfs(l -> left, r->right)&&dfs(l->right,r->left);//左子树的左节点与右子树的右节点
    }
};
class Solution {
public:
//(迭代) O(n) 用栈模拟递归
    bool isSymmetric(TreeNode* root) {
        if (!root) return true;
        stack<TreeNode*> stk;
        stk.push(root->left);
        stk.push(root->right);
        while(stk.size())
        {
            auto left = stk.top();//成对取出
            stk.pop();
            auto right = stk.top();
            stk.pop();
            if(left==NULL&&right==NULL) continue;
            if(!left||!right) return false;
            if(left->val!=right->val) return false;
            stk.push(left->left);//对应插入,注意顺序
            stk.push(right->right);
            stk.push(left->right);
            stk.push(right->left);
        }
        return true;
    }
};

AcWing 40. 顺时针打印矩阵

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

            int a = x + dx[d], b = y + dy[d];
            if (a < 0 || a >= n || b < 0 || b >= m || st[a][b])
            {
                d = (d + 1) % 4;
                a = x + dx[d], b = y + dy[d];
            }
            x = a, y = b;
        }
        return res;        
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园建设方案旨在通过融合先进技术,如物联网、大数据、人工智能等,实现校园的智能化管理与服务。政策的推动和技术的成熟为智慧校园的发展提供了基础。该方案强调了数据的重要性,提出通过数据的整合、开放和共享,构建产学研资用联动的服务体系,以促进校园的精细化治理。 智慧校园的核心建设任务包括数据标准体系和应用标准体系的建设,以及信息化安全与等级保护的实施。方案提出了一站式服务大厅和移动校园的概念,通过整合校内外资源,实现资源共享平台和产教融合就业平台的建设。此外,校园大脑的构建是实现智慧校园的关键,它涉及到数据中心化、数据资产化和数据业务化,以数据驱动业务自动化和智能化。 技术应用方面,方案提出了物联网平台、5G网络、人工智能平台等新技术的融合应用,以打造多场景融合的智慧校园大脑。这包括智慧教室、智慧实验室、智慧图书馆、智慧党建等多领域的智能化应用,旨在提升教学、科研、管理和服务的效率和质量。 在实施层面,智慧校园建设需要统筹规划和分步实施,确保项目的可行性和有效性。方案提出了主题梳理、场景梳理和数据梳理的方法,以及现有技术支持和项目分级的考虑,以导智慧校园的建设。 最后,智慧校园建设的成功依赖于开放、协同和融合的组织建设。通过战略咨询、分步实施、生态建设和短板补充,可以构建符合学校特色的生态链,实现智慧校园的长远发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值