剑指offer第一周(acwing打卡)

13. 找出数组中重复的数字(小学奥数)

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

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

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

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

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

返回 2 或 3。

返回 2 或 3。


第一次写法和第二次写法思路一样,是最优思路.

第一次写法:

class Solution {
public:
//stl中有vector.size()函数.
    int duplicateInArray(vector<int>& nums) {
    //题中没有给n,所以需要自己重新定义n
        int n=nums.size();
    //整体走一遍,确定没有不在0到n-1范围的数
        for(int i=0;i<n;i++){
            if(nums[i]<0 || nums[i]>=n) return -1;
        }
    //这才是重点,如果nums[i]没在合适的位置上(nums[i]!=i),且nums[i]应该在的位置上的数不等于nums[i],那就把nums[i]和合适位置上的数做个交换.然后i不变,继续执行while.
    //如果nums[i]没在合适位置上,但合适位置上的那个数与nums[i]相等,那么就找到相等的数了
    //如果nums[i]在合适的位置上,那么i++
        for(int i=0;i<n;i++){
            while(nums[i]!=i && nums[i]!=nums[nums[i]]) swap(nums[i],nums[nums[i]]);
            if(nums[i]!=i && nums[i]==nums[nums[i]]) return nums[i];
        }
        return -1;
    }
};

第二次写法:(推荐)

我觉得这种写法还简单易理解些.重点是while循环,先看是否放在合适的位置上,若是则i++然后看下一个数,若不是,那么就看合适位置上的数是否与之相等,相等则找到,不相等则把它放到合适的位置(交换顺序),然后重复while循环.

class Solution {
public:
//自己写了个swap函数,但其实stl里面自己就带了.
    void swap(int& a,int& b){
        int t=a;
        a=b;
        b=t;;
        return;
    }
    int duplicateInArray(vector<int>& nums) {
        int n=nums.size();
        for(int i=0;i<nums.size();i++){
            if(nums[i]<0||nums[i]>n-1){
                return -1;
            }
        }
        
        int i=0;
        while(i<=n-1){
            if(nums[i]==i){
                i++;
                continue;
            }
            if(nums[i]==nums[nums[i]]){
                return nums[i];
            }
            swap(nums[i],nums[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) 的额外空间,该怎么做呢?

无脑穷举

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

最简做法

二分法详解

请添加图片描述
但不管左边是不是多一个,只要管好if里面的条件即可,看是要左半边还是要右半边.要是要左半边的话,那就是让r=mid.要是要右半边的话,那就是让l=mid+1.

二分法模板

当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。

//要先确定l和r.
int bsearch_1(int l, int r)
{
//执行循环的条件是l<r
    while (l < r)
    {
    //计算mid,左半边为[l,mid]右半边为[mid+1,r].左半边可能比右半边多一个,但是无所谓
        int mid = l + r >> 1;
        //只要管好选择左右半边的条件是啥就行了(即为模板中的check函数)
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    //最后返回l或者r都可以,mid可能不行.
    return l;
}

当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

题解代码

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n=nums.size()-1;
        int l=1,r=n;
        while(l<r){
            int mid=l+r >>1;//[l,mid],[mid+1,r]
            // cout<<"mid="<<mid;
            int count=0;
            for(auto x:nums) count+=x>=l&&x<=mid;
            if(count>mid-l+1) r=mid;
            else l=mid+1;
        }
        //return l或者r都可以.因为l==r
        return l;
        
    }
};

知识 二分到最后只剩下两个数时

请添加图片描述

语法 auto循环

for(auto x:nums) cout<<x
此时x以次为nums里的元素,而非nums[x]以次为nums里的元素

找错误

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n=nums.size()-1;
        int l=1,r=n;
        while(l<r){
            int mid=l+r >>1;//[l,mid],[mid+1,r]
            cout<<"mid="<<mid;
            int count=0;
            for(auto x:nums) count+=x>=l&&x<=mid;
            if(count>mid-l+1) r=mid;
            else l=mid+1;
        }
        return r;
        
    }
};

错误的地方在于,应为for(auto x:nums) count+=x>=1&&x<=mid;是1而不是l,因为数的是值从l到mid的范围,而不是1到mid的范围

15. 二维数组中的查找

题目

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

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

样例
输入数组:

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

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

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

思路

最笨的做法就是穷举整个数组,但是会超时,随意要利用上这个数组的性质:同一行从左到右逐渐变大,同一列从上到下逐渐变大.
所以,数组中最右边那一列的数都比较特殊:
在这里插入图片描述

判断二维数组是否是空(技巧)

		int h=array.size();
        if(h==0) return false;
        int l=array[0].size();
        if(l==0) return false;

最优代码

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

最优代码(找错误Segmentation Fault)

class Solution {
public:
    bool searchArray(vector<vector<int>> array, int target) {
        int h=array.size();
        if(h==0) return false;
        int l=array[0].size();
        int i=0,j=l;
        while(i<=h&&j>=0){
            if(array[i][j]==target) return true;
            else if(array[i][j]>target) j--;
            else i++;
        }
        return false;
    }
};

报的错误为Segmentation Fault.段错误就是指访问的内存超出了系统所给这个程序的内存空间,访问了内存不被允许的空间.上面代码为数组指针越界访问.
起初j的值应该为l-1,而非l,因为l为一位数组的size,所允许访问的空间为0到size-1.
同理,while循环也有错误,i最大应该为h-1,而非h

16. 替换空格

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

你可以假定输入字符串的长度最大是 1000。

注意输出字符串的长度可能大于 1000。

样例
输入:“We are happy.”

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

最优代码

思路:遍历整个字符串,用count记录空格的数量,因为一个空格要被替换成"%20"三个字符,所以要把str扩容到(resize)原始长度+2*count.
在这里插入图片描述

class Solution {
public:
    string replaceSpaces(string &str) {
        int count=0;
        int ori_len=str.size();
        for(int i=0;i<ori_len;i++){
            if(str[i]==' '){
                count++;
            }
        }
        int cur_len=ori_len+2*count;
        str.resize(cur_len);
        int i=ori_len-1,j=cur_len-1;
        while(i<j){
            if(str[i]==' '){
                str[j--]='0';
                str[j--]='2';
                str[j--]='%';
                i--;
            }else{
                str[j--]=str[i--];
            }
        }
        return str;
    }
};

17. 从尾到头打印链表

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

返回的结果用数组存储。

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

学到的stl知识

vector的末尾插入为push_back,stack为push.
stack读末尾元素为.top() .pop()
vector用[]和pop
空都为empty
head为一个指针,直接while(head){head=head->next}就行.

数组倒个,直接用vector(a.rbegin(),a.rend())左闭右开
begin和end成员
都要有左闭右开的思想

    begin和end操作产生指向容器内第一个元素和最后一个元素的下一个位置的迭代器,如下所示。这两个迭代器通常用于标记包含容器中所有元素的迭代范围。

.begin() 返回一个迭代器,它指向容器c的第一个元素

.end() 返回一个迭代器,它指向容器c的最后一个元素的下一个位置

.rbegin() 返回一个逆序迭代器,它指向容器c的最后一个元素

.rend() 返回一个逆序迭代器,它指向容器c的第一个元素前面的位置

题解

这题没啥难的,咋做都行,就是要熟练用stl的vector和stack.用vector初始化的方法返回最终数组更省事些,大雪莱nb.
大雪莱的代码:

/**
 * 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> a;
        while(head){
            a.push_back(head->val);
            head=head->next;
        }
        return vector<int>(a.rbegin(),a.rend());
    }
};

我的代码:看看就行,思路一样,但是对c++语言运用功底比人家差远了.

/**
 * 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) {
        stack<int> a;
        vector<int> b;
        while(head!=NULL){//C++为NULL
            a.push(head->val);
            head=head->next;
        }
        while(!a.empty()){
            b.push_back(a.top());
            a.pop();
        }
        return b;
    }
};

18重建二叉树

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

注意:

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

返回:[3, 9, 20, null, null, 15, 7, null, null, null, null]
返回的二叉树如下所示:
在这里插入图片描述

思路:

学了个auto的用法.
递归嘛,题目难点在于左右字数前中序遍历区间的划分,如图示:

请添加图片描述

最优解:

/**
 * 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> preorder,inorder;
    map<int,int> hash;
    TreeNode* dfs(int pl,int pr,int il,int ir){
        if(pl>pr) return nullptr;
        //auto的用法
        auto root=new TreeNode(preorder[pl]);
        //从hash表中查到该值在中序排列中的位置k
        int k=hash[root->val];
        //看上面我画的图
        auto left =dfs(pl+1,pl+k-il,il,k-1);
        auto right=dfs(pl+k-il+1,pr,k+1,ir);
        root->left=left;
        root->right=right;
        return root;
    }
    TreeNode* buildTree(vector<int>& _preorder, vector<int>& _inorder) {
        preorder=_preorder;
        inorder=_inorder;
        //做一个hash表k来记录每个值在哪个位置
        for(int i=0;i<inorder.size();i++) hash[inorder[i]]=i;
        return dfs(0,preorder.size()-1,0,inorder.size()-1);
    }
};

19二叉树的下一个节点

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

注意:

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

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

解释:该二叉树的结构如下,2的后继节点是3。
在这里插入图片描述

思路:

学了个if(p->right!=NULL)等效为if(p->right!=NULL)

找中序遍历一个节点的下一个节点,把中序遍历看成排序树的话,那就是找比这个数大一点点的数.分为两种情况:
1.若节点有右子树,那就为右子树最左边的那个节点,即在右子树中不断往左子树节点方向发展
2.若该节点没有右子树,那么就为他所有祖宗节点中第一个排在他右面的,即p->father->left==p.
若2也没有,那么这个节点就已经是最靠右的节点了,返回null.

最优解:

/**
 * 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->right){也可以
        if(p->right!=NULL){
           p=p->right;
           while(p->left!=NULL){
               p=p->left;
           }
           return p;
        }else if(p->father!=NULL){
        while(p->father!=NULL){
            if(p->father->left==p){
                return p->father;
            }
            p=p->father;
        }
    }
    return NULL;
    }
};

21斐波那契数列

思路

有好多种办法,大雪莱矩阵那种没看懂.下面这种用了数组,可以存数,减少了重复运算.
挖个坑,看大雪莱文章
大雪莱文章-求解斐波那契数列的若干方法
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
要注意的是,a[100]定义为类的共有变量,但是a[0]=0这样的代码是不能写在那个区域的,只能写在函数里.

第二优解

class Solution {
public:
    int a[100];
    int Fibonacci(int n) {
        a[0]=0;
        a[1]=1;
        a[2]=1;
        if(a[n]||n==0){
            // cout<<"n="<<a[n];
            return a[n];
            
        } 
        if(n<0) return -1;
        else{
            a[n]=Fibonacci(n-1)+Fibonacci(n-2);
            return a[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) {
        //只有一个元素比左面的元素小.
        if(nums.size()==1){
            return nums[0];//wocao真就这么过了
        }
        for(int i=0;i<nums.size();i++){
            if(nums[i]>nums[i+1]) return nums[i+1];
        }
        return -1;
    }
};

23矩阵中的路径

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

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

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

注意:

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

str=“BCCE” , return “true”

str=“ASAE” , return “false”

思路:

见代码注释.我学到了四个方向枚举用dx dy两个数组.

最优解:

class Solution {
public:
    string str;
    bool hasPath(vector<vector<char>>& matrix, string &str1) {
        str=str1;
        for(int i=0;i<matrix.size();i++){
            for(int j=0;j<matrix[i].size();j++){
                if(dfs(matrix,0,i,j)) return true;
            }
        }
        return false;
    }
    bool dfs(vector<vector<char>> matrix,int u,int i,int j){
        //若matrix[i][j]与字符串u位置处不相等,直接false;
        if(matrix[i][j]!=str[u]){
            return false;
        }
        //上面匹配到str[u]了,若u刚好为str最后一个元素则匹配成功,直接true;
        if(u==str.size()-1){
            return true;
        }
        // 路径不能走回头路,所以把走过的弄成*,要注意的是函数声明中matrix应为复制传参而非引用传参
        matrix[i][j]='*';
        //从大雪莱那里学到的,上下左右四个方向枚举的写法
        int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
        for(int a=0;a<4;a++){
            ///刚开始写成int i+=dx[a]了,聪明的你知道错在哪里吗?(这样的话i就会累加了,就不是从起始位置分别上下左右四种情况,而是从起始上下左右移动了)
            int m=i+dx[a];
            int n=j+dy[a];
            if(m>=0&&m<matrix.size()&&n>=0&&n<matrix[i].size()){
                if(dfs(matrix,u+1,m,n)){
                    return true;
                }
            }
        }//
        return false;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值