剑指offer

本文概述了解决剑指 Offer 中涉及的几个算法问题:复杂链表复制、字符串替换空格、左旋转字符串、查找数字、缺失数字、二维数组查找、最小数字、唯一字符等。通过递归和哈希表等方法,详细展示了如何实现这些经典面试题目的解决方案。

20221226

剑指 Offer 35. 复杂链表的复制

解题思路:首先,复制链表节点插入到原节点的后面,例如构建A->A*->B->B*->C->C*。2.将random与的关系连接到复制的节点上。3.将next的关系加上,将链表分割为2个链表。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
      if(head==nullptr)
      return nullptr;

    for(Node* ptr=head;ptr!=nullptr;ptr=ptr->next->next)  //将复制链表节点插入到原节点的后面
    {
        Node* newNode=new Node(ptr->val);
      newNode->next=ptr->next; 
        ptr->next=newNode; 
    }

      for(Node* ptr=head;ptr!=nullptr;ptr=ptr->next->next) //将random与的关系连接到复制的节点上。
      {
         Node* tmp=ptr->next;
         if(ptr->random!=nullptr)
         tmp->random=ptr->random->next;
         else
         tmp->random=nullptr;
      }  
        Node* newHead=head->next;
        for(Node* node=head;node!=nullptr;node=node->next)  //3.将next的关系加上,将链表分割为2个链表。
        {
            Node* nodeNew=node->next;
            node->next=node->next->next;
            if(nodeNew->next!=nullptr)
            nodeNew->next=nodeNew->next->next;
            else
            nodeNew->next=nullptr;
        }
            return newHead;
    }
};

剑指 Offer 05. 替换空格

首先,我们先遍历一边字符数组,找到其中空格的个数,因为一个空格用三个字符表示,所以一个空格我们需要增加2个字符,因为加上空格后就是3个字符。所以我们把数组的长度增加为s.resize(s.size()+2*n);n为空格数量。

分别设置2个指针i,j指向原来数组的最后一个字符和新数组的最后一个字符,实现修改。当i指针不为空格式时,让s[j]=s[i];
当i指针为空格时,让s[j]=‘0’; s[j-1]=‘2’; s[j-2]=‘%’;


class Solution {
public:
    string replaceSpace(string s) {

int len=s.size();
int count=0;
for(int i=0;i<s.size();i++)
{
if(s[i]==' ')
count++;
}

s.resize(len+count*2);
for(int i=len-1,j=s.size()-1;i<j;i--,j--)
{
    if(s[i]!=' ')
    s[j]=s[i];
    else{
        s[j]='0';
        s[j-1]='2';
        s[j-2]='%';
        j=j-2;
    }
}
return s;

    }
};

剑指 Offer 58 - II. 左旋转字符串

进行3次反转:
1.首先是0-----> n-1位置进行字母的反转。
2.从n---------> s.size()-1 位置进行字母的反转。
3.从0---------> s.size()-1 位置进行字母的反转。

class Solution {
public:
    void reverses(string& s,int left,int right){
         char temp;
        while(left<right){
           temp=s[left];
           s[left]=s[right];
           s[right]=temp;
           left++;
           right--;
        }
    }
    string reverseLeftWords(string s, int n) {
            reverses(s,0,n-1);
            reverses(s,n,s.size()-1);
            reverses(s,0,s.size()-1);
            return s;
    }
};

剑指 Offer 03. 数组中重复的数字

使用哈希表,unordered_map<int,bool> map,每遍历一个数据元素,将其对应的bool值赋值为true,当相同的元素出现时,这个元素对应的bool值为true,我们返回这个值即可。

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
unordered_map<int,bool> map;
for(int num:nums)
{
    if(map[num])
    return num;

    map[num]=true;
}
return -1;
    }
};

20221230

剑指 Offer 53 - I. 在排序数组中查找数字 I

使用二分查找方法。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int i=0;
        int j=nums.size()-1;
        while(i<=j)
        {
            int m=(i+j)/2;
            if(nums[m]<=target)
            i=m+1;
            else
            j=m-1;

        }
int right=i;
i=0;
j-nums.size()-1;
   while(i<=j)
        {
            int m=(i+j)/2;
            if(nums[m]<target)
            i=m+1;
            else
            j=m-1;
        }
int left=j;
return right-left-1;
    }
};

剑指 Offer 53 - II. 0~n-1中缺失的数字

解题思路:
方法一:使用哈希表,将数组当中的元素放入到哈希表中,遍历从0-n这n+1个元素,找出没有出现的那个元素。

class Solution {
public:
    int missingNumber(vector<int>& nums) {
            unordered_set<int> set;
            for(int i=0;i<nums.size();i++)
            {
                set.insert(nums[i]);
            }

        int missing=-1;
        
        for(int i=0;i<nums.size()+1;i++)
        {
            if(set.count(i)==0)
            {
                missing=i;
                break;
            }
        }
return missing;

    }
};

法二:
使用直接法

class Solution {
public:
    int missingNumber(vector<int>& nums) {
int  len=nums.size()+1;

int count;
for(int i=0;i<len-1;i++)
{
    if(nums[i]!=i)
        return i;
}

return len-1;
    }
};

20230102

剑指 Offer 04. 二维数组中的查找

解题思路:首先找到二维矩阵的左下角元素,判断matrix[i][j]与target的大小,若matrix[i][j]>target,则i–,若matrix[i][j]<target,则j++,

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

    }
};

剑指 Offer 11. 旋转数组的最小数字

解题思路:我们先找出mid=(left+right)/2,若numbers[mid]>numbers[right],则让left=mid+1,numbers[mid]<numbers[right],则让right=mid,若numbers[mid]==numbers[right],则让right-=1

class Solution {
public:
    int minArray(vector<int>& numbers) {
            int left=0;
            int right=numbers.size()-1;
            while(left<right)
            {
                int mid=left+(right-left)/2;
                if(numbers[mid]<numbers[right])
                right=mid;
                else
                if(numbers[mid]>numbers[right])
             left=mid+1;
                else
                right-=1;

            }
        return numbers[left];
    }
};

剑指 Offer 50. 第一个只出现一次的字符

解题思路:
使用哈希表存储存放的次数
我们可以对字符串进行2次遍历,使用哈希映射来统计每个字符出现的次数。在第二次遍历时,我们只要遍历到了一个只出现一次的字符,那么就返回该字符,否则在遍历结束后返回空格。

class Solution {
public:
    char firstUniqChar(string s) {
unordered_map<int,int> map;
for(char c:s)
    {
        map[c]++;
    }
    for(char c:s)
    {
        if(map[c]==1)
        return c;
    }
    return ' ';
    }
};

20230103

剑指 Offer 32 - I. 从上到下打印二叉树

使用层序遍历的方法,利用队列来进行排序。

/**
 * 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> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<int> nums;
TreeNode* tmp=root;
que.push(tmp);

if(root==nullptr)
return nums;


while(que.empty()==false)
{

    int size=que.size();
    for(int i=0;i<size;i++)
    {
         TreeNode *node=que.front();
nums.push_back(node->val);
que.pop();
if(node->left!=nullptr)
que.push(node->left);
if(node->right!=nullptr)
que.push(node->right);
    } 
}
return nums;
    }
};

剑指 Offer 32 - II. 从上到下打印二叉树 II

采用层序遍历的方式来进行遍历二叉树

/**
 * 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>> levelOrder(TreeNode* root) {



vector<vector<int>> nums;

if(root==nullptr)
return nums;

queue<TreeNode*> que;
TreeNode* ptr=root;
que.push(ptr);

while(que.empty()==false)
{
    int size=que.size();
    vector<int> vec;
    for(int i=0;i<size;i++)
    {
        TreeNode* node=que.front();
        que.pop();
      vec.push_back(node->val);
      if(node->left!=nullptr)
      que.push(node->left);
      if(node->right!=nullptr)
      que.push(node->right);
   }
   nums.push_back(vec);
}

return nums;

    }
};

剑指 Offer 32 - III. 从上到下打印二叉树 III

创建一个标志isOrderLeft,当遍历的层数是奇数层时,按照从左到右的顺序遍历,当遍历的层数为偶数层时,按照从右到左的顺序遍历,创建一个双端队列,当遍历的层数是奇数层时,从下端进入队列,当遍历的层数为偶数层时,从上端进入队列。
代码:

/**
 * 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>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;

        if(root==nullptr)
        return ans;

        queue<TreeNode*> que;
        que.push(root);
        bool isOrderLeft=true;

        while(que.empty()==false)
        {
            deque<int> list;
           
            int size=que.size();
            for(int i=0;i<size;i++)
            {
                TreeNode* node=que.front();
                que.pop();
                if(isOrderLeft)
                {
                    list.push_back(node->val);
                }
                else
                list.push_front(node->val);
                if(node->left!=nullptr)
                que.push(node->left);
                if(node->right!=nullptr)
                que.push(node->right);
            }
             vector<int> vec(list.begin(),list.end());
             ans.push_back(vec);
             isOrderLeft=!isOrderLeft;
        }

return ans;
    }
};

剑指 Offer 27. 二叉树的镜像

可以使用前序和后序遍历进行交换,但不能使用中序遍历。将根节点的左子树和右子树进行交换。
后序遍历递归法:

/**
 * 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 swap(TreeNode* root){
         if(root==nullptr)
        return; 
        
        swap(root->left);
        swap(root->right);
       TreeNode* temp;
        temp=root->left;
        root->left=root->right;
        root->right=temp;
       
    }

    TreeNode* mirrorTree(TreeNode* root) {
swap(root);
return root;
    }
};

前序遍历的方法:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
if(root==nullptr)
return root;
TreeNode* temp=root->left;
root->left=root->right;
root->right=temp;
invertTree(root->left);
invertTree(root->right);
return root;
    }
};


20230104

剑指 Offer 28. 对称的二叉树

解题思路:
传递的参数为左子树与右子树

1.参数:因为我们要比较的是根节点的2个子树是否相互翻转,进而判断这个树是不是对称树,所以参数为左子树与右子树节点。

2.终止条件:要比较2个节点数值相不相同,节点为空的情况有:

左节点为空,右节点不为空,不对称,return false
左不为空,右为空,不对称 return false
左右都为空,对称,返回true

3.确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 右节点都不为空,且数值相同的情况。

比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
如果左右都对称就返回true ,有一侧不对称就返回false 。

完整代码:

/**
 * 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 check(TreeNode* left,TreeNode* right){
         // 首先排除空节点的情况
        if(left==nullptr&&right!=nullptr)
        return false;
      else  if(left!=nullptr&&right==nullptr)
        return false;
        else if(left==nullptr&&right==nullptr)
        return true;
        // 排除了空节点,再排除数值不相同的情况
        else if(left->val!=right->val)
        return false;
       
           // 此时就是:左右节点都不为空,且数值相同的情况
        // 此时才做递归,做下一层的判断
        else{
             bool oneSide=check(left->left,right->right);
        bool twoSide=check(left->right,right->left);
        bool isSame=oneSide&&twoSide;
        return isSame;
        }
       
    }
    bool isSymmetric(TreeNode* root) {
bool isSames=check(root->left,root->right);
return isSames;
    }
};

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

解题思路:从地面到第一层台阶有1种跳法,从地面到第二层台阶有2种跳法,我们创建dp数组,dp[i]用于表示从地面到第i层台阶的有多少种跳法,dp数组的长度设置为n+1,序列为从dp[0,1,2…n]。从地面到第三层的跳法种类即为从第一层跳2个台阶到第三层或从第二层跳1个台阶到第三层,即为dp[3]=dp[2]+dp[1]。那么从地面到第n层的跳法种类即为从第n-2层跳2个台阶到第n层或从第n-1层跳1个台阶到第n层,即为dp[n]=dp[n-2]+dp[n-1]。
代码:

class Solution {
public:
    int numWays(int n) {
if(n<=1)
return 1;
vector<int> dp(n+1);

dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++)
{
    dp[i]=(dp[i-1]+dp[i-2])%(1000000007);;
}
return dp[n];

    }
};

20230412

剑指 Offer 63. 股票的最大利润

贪心算法
我们来假设自己来购买股票。随着时间的推移,每天我们都可以选择出售股票与否。那么,假设在第 i 天,如果我们要在今天卖股票,那么我们能赚多少钱呢?

显然,如果我们真的在买卖股票,我们肯定会想:如果我是在历史最低点买的股票就好了!太好了,在题目中,我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice。

因此,我们只需要遍历价格数组一遍,记录历史最低点,然后在每一天考虑这么一个问题:如果我是在历史最低点买进的,那么我今天卖出能赚多少钱?当考虑完所有天数之时,我们就得到了最好的答案。

c++

class Solution {
public:
    int maxProfit(vector<int>& prices) {
int low=INT_MAX;
int result=0;
for(int i=0;i<prices.size();i++)
{
    low=min(low,prices[i]);
result=max(result,prices[i]-low);
}
return result;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值