剑指offer

本文涵盖了多种算法的实践应用,包括数组中找重复数、有序二维数组目标查找、字符串处理、链表反转、二叉树重建、队列模拟、斐波那契数列、跳跃游戏、旋转数组最小值、矩阵路径搜索以及绳子切割问题。这些算法涉及到数组、链表、栈、递归、二分查找、动态规划等多种数据结构和算法技巧。
摘要由CSDN通过智能技术生成

1.找重复数:

长度n的数组,有0~n-1的数字;

第一次遇到数字 xx 时,将其交换至索引 xx 处;而当第二次遇到数字 xx 时,一定有 nums[x] = xnums[x]=x ,此时即可得到一组重复数字。

每个数字都有自己的位置 num[i]=i ,位置不正确就换位置num[i]=num[num[i]]

for(int i=0;i<numbers.size();i++)
        {
            if(numbers[i]==i)
                continue;
            else{
                if(numbers[i]==numbers[numbers[i]])
                    return numbers[i];
                else
                    swap(numbers[i],numbers[numbers[i]]);
                    //int temp=nums[i];
                    //nums[i]=nums[nums[i]];
                    //nums[temp]=temp;
            }
        }
        return -1;
    }

2.有序二维数组找到目标:

先检查数组是否合法;

数组是有序的,需要有目标的移动、查找目标,

目标过大,往右查找;目标过小,往上查找

int n= array.size();
        int m=array[0].size();
        if(n==0)  { return false; }
        if(m==0) { return false;}
        
        for(int i=n-1, j=0;i>=0&&j<m;)
        {
            if(target>array[i][j])
            { j++; }
            else if (target<array[i][j])
            { i--; }
            else
            {return true; }
        }
        return false;

3.从string中找到空格,然后替换“123”:

string add_s="123";

生成空白string,用加法来承接元素;

判断string里的char是不是空格;string[i]=='  '

int len=s.size();
string ret="";
string add_s="%20";
    
for(int i=0;i<len;i++)
{
    if(s[i]!=' ') {
        ret=ret+s[i];
    }
    else{
        ret=ret+add_s;
    }
}
return ret;

4.反转链表

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

先反转链表,再插入到vector

vector<int> reversePrint(ListNode* head) {
        vector<int>v;
        // 空链表
        if (head == nullptr) return v;
        //先反转链表
        ListNode* CURR=head->next;
        ListNode* temp;
        head->next=nullptr;//头节点当作尾
        while(CURR){
            //反转
            temp=CURR->next;
            CURR->next=head;
            //为下一次循环做准备
            head=CURR;
            CURR=temp;
        }
        // 取出链表中的值
        while(head){
            v.push_back(head->val);
            head=head->next;
        }
        return v;
}

5.重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

二叉树前序遍历的顺序为:

先遍历根节点;随后递归地遍历左子树;最后递归地遍历右子树。

二叉树中序遍历的顺序为:

先递归地遍历左子树;随后遍历根节点;最后递归地遍历右子树

class Solution {
private:
    unordered_map<int, int> index;
public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, 
                    int pre_left,int pre_right,int in_left,int in_right){
        if(pre_left>pre_right){ return nullptr;}

        //前序第一个是根节点
        int pre_root=pre_left;//pre_root--preorder 根节点坐标位置
        int in_root=index[preorder[pre_root]];//in_root--inorder 根节点坐标位置
        //int in_root=index[preorder[pre_left]];

        //根节点建立
        TreeNode* root=new TreeNode(preorder[pre_root]);
        //TreeNode* root=new TreeNode(preorder[pre_left]);

        //左树节点数目
        int size_left_tree=in_root-in_left;
        //构建左树
        root->left=myBuildTree(preorder,inorder, pre_left+1,pre_left+size_left_tree,
                    in_left,in_root-1);
        //构建右树
        root->right=myBuildTree(preorder,inorder, pre_left+size_left_tree+1,pre_right,
                    in_root+1,in_right);

        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n=preorder.size();
        //哈希映射 定位根节点
        for(int i=0;i<n;i++){
            index[inorder[i]]=i;
        }
        return myBuildTree(preorder,inorder, 0,n-1, 0,n-1);
    }

};

6.用两个栈实现一个队列

完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

两个独立的栈,一个存储尾插,一个删除输出

队列是先进先出,等第n次的删除输出栈为空,再把存储栈的数据放入

class CQueue {
public:
    CQueue() {}
    //尾插
    void appendTail(int value) {
        this->in_data.push(value);
    }
    //删头
    int deleteHead() {
        //重点:只有输出栈为空才把存储栈的数据放进来
        if(this->out_data.empty()) {
            if(this->in_data.empty()) {
                return -1;
            }
            this->in_out();//后面存储栈已经清空
        }
        int value=this->out_data.top();
        this->out_data.pop();
        return value;
    }

private:
    //正序
    stack<int> in_data;
    //倒序
    stack<int> out_data;
    //两个栈的顺序不一样 模拟队列的进、出
    void in_out() {
        while(!in_data.empty()) {
            out_data.push(in_data.top());//把存储栈的内容倒置
            in_data.pop();//清空存储栈
        }
    }
};

7.输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。

不需要保存所有的值,所以只保留 加数、被加数 即可

class Solution {
public:
    int fib(int n) {
        if(n<2){return val[n];}
        else{
            for(int i=2;i<=n;i++){
                int temp=val[1];               
                val[1]=(val[1]+val[0])%mod_mun;
                val[0]=temp;
            }
            return val[1];
        }
    }
private:
    int mod_mun=1000000007;
    vector<int> val={0,1};
};

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

class Solution {
public:
    int numWays(int n) {
        int fun_num;
        if(n<=1){
             return 1; 
        }
        else{
            //不需要单独考虑n=2的情况
            for(int i=3;i<=n;i++){
                int temp=v1[0];
                v1[0]=v1[1];
                v1[1]=(temp+v1[1])%mod_num;
            }
            return v1[1];
        }
    }
private:
    int mod_num=1000000007;
    vector<int> v1={1,2};
};

9.旋转数组最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。给你一个可能存在重复元素值的数组 numbers ,它原来是一个  升序  排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。

旋转之后的数组一个升序数组分成两个数组 之后的拼接;由于1.顺序存储 2.元素有序 所以用二分法查找交界点,最小值在交界点的右边

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int left=0;
        int right=numbers.size()-1;
        while(left<right){
            int mid=(left+right)/2;
            //中间比最右边大,说明左半部分是有序的,分界点在右半部分
            //且mid位置不是最小值
            if(numbers[mid]>numbers[right]){
                left=mid+1;
             }
            //中间比最右边小,说明右半部分是有序的,说明分界点在左半部分
            else if(numbers[mid]<numbers[right]){
                right=mid;
            }
            //中间、最右边相等,不能判断分界点位置,所以缩小查找范围
            else{
                right=right-1;
             }
        }
        return numbers[left];
    }
};

10.矩阵中的路径

一个二维字符网格和一个字符串单词 word ;如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。

同一个单元格内的字母不允许被重复使用。

需要用到递归,先找到一个起始点,然后一直递归下去。

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int row=board.size();//行
        int col=board[0].size();//列
        //两个for循环是为了寻找 开始的位置
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++){
                if(this->fun(board, word,i,j,0)){
                    return true;
                }
            }
        }
        //没有正确的路径
        return false;
    }
private:
    //寻找的路径
    bool fun(vector<vector<char>>& board, string word,int i,int j,int k){
        int row=board.size();//行
        int col=board[0].size();//列
        //int i 寻找的行数
        //int j 寻找的列数
        //int k word的第k个字符
        //路径超出查找范围 或者 查找的字符不对
        if(i<0||i>=row||j<0||j>=col||board[i][j]!=word[k]){return false;}
        //k=word.length() 就是已经查找完毕,找到路径
        if(k==word.length()-1){return true;}
        //查找到当前需要的字符
        //先把当前位置置空,防止后面递归的时候重复使用
        board[i][j]=' ';
        //沿着当前位置的上下左右寻找
        //沿着下面的路径走下去,后面的board[i+-1][j+-1]也会置空
        bool ret=fun(board, word,i-1,j,k+1)||fun(board, word,i+1,j,k+1)||
                 fun(board, word,i,j-1,k+1)||fun(board, word,i,j+1,k+1);
        //把原先的数据恢复
        board[i][j]=word[k];
        return ret;
    }
};

11.剪绳子

一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子长度可能的最大乘积

把绳子先切一刀分成 j 和 i-j ,j 部分不切分,i-j 部分找到最大乘积

遍历 j,找到起始切分位置

class Solution {
public:
    int cuttingRope(int n) {
        //0 不是正整数,1 是最小的正整数,0 和 1 都不能拆分,因此 dp[0]=dp[1]=0
        vector<int> dp(n+1);
        dp[2]=1;
        //求长度为i的绳子的最大乘积
        for(int i=3;i<=n;i++){
            //第一刀切分成j和i-j两部分;
            //j部分不再切分 
            for(int j=1;j<i-1;j++){
                //后面的dp[i]是上一次的j得到的最大乘积
                dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));
            }
        }
        return dp[n];
    }
};

12.调整数组顺序 奇数在前,偶数在后

        求奇偶,可以%2,也可以&1(偶数为0.奇数为1)

class Solution {
public:
    vector<int> exchange(vector<int>& nums)
    {
        int left=0;
        int right=nums.size()-1;
        while(left<right){
            while(left<right && (nums[left] & 1)==1)//为奇数
            {
                left++;    
            }
            while(left<right && (nums[right] & 1)==0)//为偶数
            {
                right--;    
            }
            swap(nums[left],nums[right]);
        }
        return nums;
    }
};

13.从链表倒数第k个节点输出

a、单指针  需要计算list长度

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *node=nullptr;
        int i=0;
        //算长度
        for(node=head;node!=nullptr;node=node->next){
            i++;
        }
        //i>k 卡到倒数第k个
        for(node=head;i>k;i--){
            node=node->next;
        }
        return node;
    }
};

b、双指针 让两个指针间隔k个距离

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *p1=head;
        ListNode *p2=head;
        for(int i=0;i<k;i++){
            p1=p1->next;
        }
        //让两个指针间隔k个距离   p1到尾,p2到倒数k
        while(p1){  
            p1=p1->next;
            p2=p2->next;
        }
        return p2;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值