LeetCode

<1>两数之和

原题链接:两数之和 – 力扣(LeetCode)

思路

我们的目的是在数组中找到两个数a和b,使得a+b=target,且数组中只会存在一组a、b满足条件。

因为target已知,我们在遍历数组时可以得到数a,那么根据b=target-a,我们实际上可以在得到a的同时,得到所需要的b值,那么判断b是否已经出现即可。

那么,问题就转化为了如何在得到a的时候,查看我们是否已经遍历过b。

一个简单的思路就是我们将每次遍历得到的元素存起来,每次遍历时,我们判断其对应的数是否已经存储过,如果我们已经存储过a,那么我们返回当前下标和a的下标即可。

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        //使用unordered_map,其底层实现是哈希表,有良好的查找性能
        //第一个位置的int存储b,第二个位置的int存储下标
        unordered_map<int,int> hash;
        vector<int> result;//result用于存储最终结果
        for(int i = 0;i < nums.size();++i)
        {
            int b = target - nums[i];//得到b的值
            if(hash.find(b) != hash.end())//判断b是否已经遍历过
            {
                //遍历过则输出结果
                result.push_back(hash[b]);//b的下标
                result.push_back(i);//当前下标
                return result;
            }
            hash[nums[i]] = i;//没有遍历过则将当前下标存储在unordered_map中
        }
        return result;
    }
};

<2>两数相加

原题链接:两数相加

思路

我们的目的是将两个链表的数字相加得到结果链表。

我们可以同步遍历两个链表,将每一位的数字相加,需要注意的是进位信息。

当一个链表结束时,我们要判断另一个链表是否也结束,如果没有,则需要继续进行,这里需要特别注意进位信息是有可能传递到高位的。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* l3 = new ListNode(0,nullptr);//先生成结果链表的第一个节点
        int flag = 0,first = 1;//flag表示是否存在进位,first表示是否是第一位的运算
        ListNode* p = l3;//使用p代替l3参与遍历,这样我们返回l3即可
        while(l1 != nullptr && l2 != nullptr)//当两个链表都不为空时,遍历每一位
        {
            int sum = l1->val + l2->val + flag;//这一位的结果是两个链表的和再加上进位
            if(sum >= 10)
            {
                sum = sum 
                flag = 1;//产生进位
            }
            else
            {
                flag = 0;//不产生进位
            }
            if(first == 1)//因为我们提前new了一个节点,所以第一位运算无需new新节点
            {
                p->val = sum;
                p->next = nullptr;
                first = 0;
            }
            else//不是第一位运算,我们就正常按照链表的尾插进行
            {
                ListNode* q = new ListNode(sum,nullptr);
                p->next = q;
                p = q;
            }
            l1 = l1->next;
            l2 = l2->next;
        }
        while(l1 != nullptr)//当l2为空但l1不为空时
        {
            //与上面的逻辑一样,只是将l2的节点视为0
            int sum = l1->val + flag;
            if(sum >= 10)
            {
                sum = sum 
                flag = 1;
            }
            else
            {
                flag = 0;
            }
            //因为题目假设没有空链表,所以进入此处时一定不是第一位运算
            ListNode* q = new ListNode(sum,nullptr);
            p->next = q;
            p = q;
            l1 = l1->next;
        }
        while(l2 != nullptr)//当l1为空但l2不为空时
        {
            //与上方同理
            int sum = l2->val + flag;
            if(sum >= 10)
            {
                sum = sum 
                flag = 1;
            }
            else
            {
                flag = 0;
            }
            ListNode* q = new ListNode(sum,nullptr);
            p->next = q;
            p = q;
            l2 = l2->next;
        }
        if(flag == 1)//如果最高位运算之后,扔存在进位信息,结果需要再进一位
        {
            ListNode* q = new ListNode(1,nullptr);
            p->next = q;
        }
        return l3;
    }
};

<3>无重复字符串的最长子串

原题链接:无重复字符的最长子串

思路

我们的目的是找到字符串中的无重复的最长子串。

那么,我们只需要遍历字符串,当出现重复的字符时,之前已经遍历的字符就形成一个无重复字符的子串。

我们将新的重复的字符添加进子串中,同时需要丢弃子串中已有的重复字符之前的所有字符,同时更新长度信息。

我们比较每次得到的无重复字符串的长度,找到最长的即可。

代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> dic;//char存储字符,int存储其所在的位置下标,整体为哈希实现查找效率高
        //length记录无重复字符的长度
        //max_length记录当前所有无重复字符的最长长度
        //head记录当前子串的起始位置
        int length = 0,max_length = 0,head = 0;
        for(int i = 0;i < s.size();++i)
        {
            auto it = dic.find(s[i]);//判断当前字符是否出现过,也就是是否重复
            if(it == dic.end())//没有出现过
            {
                dic[s[i]] = i;//存储字符与下标
                ++length;//增加长度
            }
            else//出现过
            {
                if(it -> second < head)//出现过,但不在当前子串的范围内,视为没有重复
                {
                    dic[s[i]] = i;
                    ++length;
                }
                else//出现过,且在当前子串内重复
                {
                    head = it->second + 1;//更新子串的起始位置为之前重复字符的下一位
                    length = i - head + 1;//添加新的字符,并更新长度
                    dic[s[i]] = i;
                }
            }
            if(length > max_length)//每次判断是否需要更新最长长度
            {
                max_length = length;
            }
        }
        return max_length;
    }
};

<4>寻找两个正序数组的中位数

原题链接:寻找两个正序数组的中位数

思路

我们的目的是找到两个正序数组的中位数,中位数的特点是左右两侧的元素个数是相同的

我们的目的就转换为将数组分割为两个部分,满足以下条件:

  • 1.左侧部分和右侧部分的元素个数是相同的
  • 2.左侧部分的所有元素都小于右侧部分的所有元素

我们暂且假设两个数组的元素个数都是偶数个,那么我们能够在两个数组中各自找到一个分割线,我们记为数组一的分割线s1和数组二的分割线s2:

  • s1的左侧加上s2的左侧,构成了左侧部分
  • s1的右侧加上s2的右侧,构成了右侧部分

接下来我们想办法满足上面的两个条件:

  • 条件一:元素个数相同的条件是比较好满足的,我们只需要分别取两个数组的中间位置作为分割线即可
  • 条件二:我们需要满足左侧小于右侧的条件,最终我们希望得到的是s1的左侧都是左侧部分的元素,s2的右侧都是右侧部分的元素

那么,我们可以通过移动分割线的方式来满足条件二,注意我们需要同时移动两个分割线来保证满足条件一。我们可以通过判断分割线左右两侧的元素来确定分割线的移动方向,我们将s1左侧的元素记为l1,右侧的元素记为r1,s2左侧的元素记为l2,右侧的元素记为r2:

  • 当l1>r2时,说明l1理应属于整体右侧部分,但目前属于左侧;相对地,r2理应属于整体左侧部分,但目前属于右侧。此时,我们就左移s1,右移s2,达到交换l1和r2的目的
  • 当l2>r1时,说明l2理应属于整体右侧部分,但目前属于左侧;相对地,r1理应属于整体左侧部分,但目前属于右侧。此时,我们就右移s1,左移s2,达到交换l2和r1的目的
  • 由于两个数组是有序的,存在前置条件l1<r1,l2<r2,简单推理可知,以上两种情况同时只能有一种成立

当两种情况都不成立时,说明左侧所有元素小于右侧所有元素,满足了条件二。此时,我们取左侧的最大值和右侧的最小值,即可算出中位数。

以上是两种数组元素个数都是偶数的情况,那么对于元素个数为奇数时,我们实际上可以将数组中每个元素重复一次,达到两个数组的元素都是偶数的情况:

  • 例如:[1,2,3],重复为[1,1,2,2,3,3]
  • 我们以重复之后的数组为分割用的数组,分割线的位置就是原数组的长度,也就是3,此时分割线在重复后数组下标为3的元素的左侧
  • 我们可以得到,分割线左侧元素在原数组的下标为$(3-1)/2$,右侧元素在原数组的下标为$3/2$
  • 那么,基于上面的下标规律,我们就可以在实际存储中不存储重复数组,仅在逻辑上使用它

接下来,我们考虑边界情况:

  • 当s1到达数组一的最左侧时,我们将s1左侧设为int类型的最小值;到达最右侧时,我们将s1右侧设为int类型的最大值
  • 当s2到达数组一的最左侧时,我们将s2左侧设为int类型的最小值;到达最右侧时,我们将s2右侧设为int类型的最大值

当任一数组达到边界情况时,说明整个数组被分到了一侧,我们以数组一达到边界情况为例,数组二同理:

  • s1在最左侧,此时l1=INT_MIN,一定存在l1<r2;因为是s1的左移,满足左移的情况是l1>r2,因为发生移动时两种情况不同时成立,一定存在l2<r1。此时,l1<r2且l2<r1,两种情况均不成立,直接得到中位数结果
    • 若在此情况下s2在最右侧,此时r2=INT_MAX,同样满足条件;此情况下,s2不可能在最左侧
  • s1在最右侧,此时r1=INT_MAX,一定存在l2<r1;因为是s1的右移,满足右移的情况是l2>r1,因为发生移动时两种情况不同时成立,一定存在l1<r2。此时,l1<r2且l2<r1,两种情况均不成立,直接得到中位数结果
    • 若在此情况下s2在最左侧,此时r2=INT_MIN,同样满足条件;此情况下,s2不可能在最右侧

代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(),n = nums2.size();//得到两个数组的长度
        int n1_s = m,n2_s = n;//先分别取两个数组的中间进行划分,分割线为n1_s,n2_s
        int n1_l,n1_r,n2_l,n2_r;//分别表示n1_s左侧和右侧,n2_S左侧和右侧的数值
        double mid = 0;//存储最终结果的变量
        while(true)
        {
            if(n1_s == 0)//n1_s在最左侧的情况
            {
                n1_l = INT_MIN;
            }
            else
            {
                n1_l = nums1[(n1_s - 1) / 2];
            }
            if(n1_s == 2 * m)//n1_s在最右侧的情况
            {
                n1_r = INT_MAX;
            }
            else
            {
                n1_r = nums1[n1_s / 2];
            }
            if(n2_s == 0)//n2_s在最左侧的情况
            {
                n2_l = INT_MIN;
            }
            else
            {
                n2_l = nums2[(n2_s - 1) / 2];
            }
            if(n2_s == 2 * n)//n2_s在最右侧的情况
            {
                n2_r = INT_MAX;
            }
            else
            {
                n2_r = nums2[n2_s / 2];
            }
            if(n1_l > n2_r)//满足情况一:进行n1_s的左移和n2_s的右移
            {
                if(n1_s != 0 && n1_s != 2 * m)
                {
                    --n1_s;
                }
                if(n2_s != 0 && n2_s != 2 * n)
                {
                    ++n2_s;
                }
            }
            else if(n2_l > n1_r)//满足情况二:进行n1_s的右移和n2_s的左移
            {
                if(n1_s != 0 && n1_s != 2 * m)
                {
                    ++n1_s;
                }
                if(n2_s != 0 && n2_s != 2 * n)
                {
                    --n2_s;
                }
            }
            else if(n1_l <= n2_r && n2_l <= n1_r)//不满足移动条件,说明得到结果
            {
                double left = 0,right = 0;
                if(n1_l < n2_l)//取左侧最大值
                {
                    left = n2_l;
                }
                else
                {
                    left = n1_l;
                }
                if(n1_r < n2_r)//取右侧最小值
                {
                    right = n1_r;
                }
                else
                {
                    right = n2_r;
                }
                mid = (left + right) / 2;
                return mid;
            }
        }
    }
};
上一篇
豫ICP备2023006989号