【C++代码】模拟,动态规划,无重复字符的最长子串,寻找两个正序数组的中位数,最长回文子串

题目:两数相加

  • 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

  • 在这里插入图片描述

  • 由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry;其中,答案链表处相应位置的数字为 (n1+n2+carry) mod 10。如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个 0 。如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个 0 。

  • /**
     * 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 *head=nullptr,*tail=nullptr;
            int carry=0;
            while(l1 || l2){
          
          
                int n1 = l1?l1->val:0;
                int n2 = l2?l2->val:0;
                int sum = n1+n2+carry;
                if(!head){
          
          
                    head = tail = new ListNode(sum%10);
                }else{
          
          
                    tail->next=new ListNode(sum%10);
                    tail=tail->next;
                }
                carry=sum/10;
                if(l1){
          
          
                    l1=l1->next;
                }
                if(l2){
          
          
                    l2=l2->next;
                }
            }
            if(carry>0){
          
          
                tail->next=new ListNode(carry);
            }
            return head;
        }
    };
    
  • 时间复杂度:O(max⁡(m,n)),其中 m 和 n 分别为两个链表的长度。我们要遍历两个链表的全部位置,而处理每个位置只需要 O(1) 的时间。空间复杂度:O(1)。注意返回值不计入空间复杂度。

题目:无重复字符的最长子串

  • 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

  • 其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!我们只要把队列的左边的元素移出就行了,直到满足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解!

  • class Solution {
          
          
    public:
        int lengthOfLongestSubstring(string s) {
          
          
            if(s.size()==0){
          
          
                return 0;
            }
            unordered_set<char> temp;
            int max_one=0;
            int left=0;
            for(int i=0;i<s.size();i++){
          
          
                while(temp.find(s[i])!=temp.end()){
          
          
                    temp.erase(s[left]);
                    left++;
                }
                max_one = max(max_one,i-left+1);
                temp.insert(s[i]);
            }
            return max_one;
        }
    };
    
  • unordered_set 容器,可直译为“无序 set 容器”,即 unordered_set 容器和 set 容器很像,唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。无序集合(unordered_set)是一种使用哈希表实现的无序关联容器,其中键被哈希到哈希表的索引位置,因此插入操作总是随机的。无序集合上的所有操作在平均情况下都具有常数时间复杂度O(1),但在最坏情况下,时间复杂度可以达到线性时间O(n),这取决于内部使用的哈希函数,但实际上它们表现非常出色,通常提供常数时间的查找操作。

题目:寻找两个正序数组的中位数

  • 给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

  • 暴力求解:使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。时间复杂度是 O(m+n),空间复杂度是 O(m+n)。

  • 不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 000 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。空间复杂度降到 O(1),但是时间复杂度仍是 O(m+n)。

  • 如果对时间复杂度的要求有 log,通常都需要用到二分查找,这道题也可以通过二分查找实现。根据中位数的定义,当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1。

  • class Solution {
          
          
    public:
    
        int help(const vector<int> &nums1,const vector<int> &nums2,int k){
          
          
            int m=nums1.size();
            int n=nums2.size();
            int index1=0,index2=0;
            while(true){
          
          
                if(index1==m){
          
          
                    return nums2[index2+k-1];
                }
                if(index2==n){
          
          
                    return nums1[index1+k-1];
                }
                if(k==1){
          
          
                    return min(nums1[index1],nums2[index2]);
                }
                int newIndex1=min(index1+k/2-1,m-1);
                int newindex2=min(index2+k/2-1,n-1);
                int p1=nums1[newIndex1];
                int p2=nums2[newindex2];
                if(p1<=p2){
          
          
                    k-=newIndex1-index1+1;
                    index1=newIndex1+1;
                }else{
          
          
                    k-=newindex2-index2+1;
                    index2=newindex2+1;
                }
            }
        }
        double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
          
          
            int len=nums1.size()+nums2.size();
            if(len&1){
          
          
                return help(nums1,nums2,(len+1)/2);
            }else{
          
          
                return (help(nums1,nums2,len/2)+help(nums1,nums2,len/2+1))/2.0;
            }
        }
    };
    
  • 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 (这里的 “/” 表示整除) , nums1 中小于等于 pivot1 的元素有 nums1[0 … k/2-2] 共计 k/2-1 个;nums2 中小于等于 pivot2 的元素有 nums2[0 … k/2-2] 共计 k/2-1 个;取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个。 这样 pivot 本身最大也只能是第 k-1 小的元素;如果 pivot = pivot1,那么 nums1[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums1 数组; 如果 pivot = pivot2,那么 nums2[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums2 数组;由于我们 “删除” 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数。

题目:最长回文子串

  • 给你一个字符串 s,找到 s 中最长的回文子串。如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

  • 对于一个子串而言,如果它是回文串,并且长度大于 222,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab”是回文串,那么 “ababa”一定是回文串,这是因为它的首尾两个字母都是 “a”。

  • 根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P(i,j) 表示字符串 s 的第 i 到 j 个字母组成的串(下文表示成 s[i:j])是否为回文串。那么我们就可以写出动态规划的状态转移方程:

    • P ( i , j ) = P ( i + 1 , j − 1 ) ∧ ( S i = = S j ) P(i,j)=P(i+1,j−1)∧(S_i ==S_j) P(i,j)=P(i+1,j1)(Si==Sj)

    • 只有 s[i+1:j−1] 是回文串,并且 s 的第 i 和 j 个字母相同时,s[i:j] 才会是回文串。

  • class Solution {
          
          
    public:
        string longestPalindrome(string s) {
          
          
            int n=s.size();
            if(n<2){
          
          
                return s;
            }
            int res=1;
            int begin=0;
            vector<vector<bool>> dp(n,vector<bool>(n));
            for(int i=0;i<n;i++){
          
          
                dp[i][i]=true;
            }
            for(int L=2;L<=n;L++){
          
          
                for(int i=0;i<n;i++){
          
          
                    int j=L+i-1;// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                    if(j>=n){
          
          
                        break;
                    }
                    if(s[i]!=s[j]){
          
          
                        dp[i][j]=false;
                    }else{
          
          
                        if(j-i<3){
          
          
                            dp[i][j]=true;
                        }else{
          
          
                            dp[i][j]=dp[i+1][j-1];
                        }
                    }
                    if(dp[i][j] && j-i+1>res){
          
          
                        res=j-i+1;
                        begin=i;
                    }
                }
            }
            return s.substr(begin,res);
        }
    };
    

题目:N 字形变换

  • 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:

    • P   A   H   N
      A P L S I I G
      Y   I   R
      
  • 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。请你实现这个将字符串进行指定行数变换的函数:string convert(string s, int numRows);

  • 设 n 为字符串 s 的长度,r=numRows。对于 r=1(只有一行)或者 r≥n(只有一列)的情况,答案与 s 相同,我们可以直接返回 s。对于其余情况,考虑创建一个二维矩阵,然后在矩阵上按 Z 字形填写字符串 s,最后逐行扫描矩阵中的非空字符,组成答案。

  • 根据题意,当我们在矩阵上填写字符时,会向下填写 r 个字符,然后向右上继续填写 r−2 个字符,最后回到第一行,因此 Z 字形变换的周期 t=r+r−2=2r−2,每个周期会占用矩阵上的 1+r−2=r−1 列。

  • 因此我们有 ⌈ n t ⌉ \Big\lceil\dfrac{n}{t}\Big\rceil tn个周期(最后一个周期视作完整周期),乘上每个周期的列数,得到矩阵的列数 c = ⌈ n t ⌉ ⋅ ( r − 1 ) c=\Big\lceil\dfrac{n}{t}\Big\rceil\cdot(r-1) c=tn(r1)。创建一个 r 行 c 列的矩阵,然后遍历字符串 s 并按 Z 字形填写。具体来说,设当前填写的位置为 (x,y),即矩阵的 x 行 y 列。初始 (x,y)=(0,0),即矩阵左上角。若当前字符下标 i 满足 i   m o d   t < r − 1 i\bmod t<r-1 imodt<r1,则向下移动,否则向右上移动。

  • class Solution {
    public:
        string convert(string s, int numRows) {
            int n=s.size(),r=numRows;
            if(r==1||r>=n){
                return s;
            }
            int t=r*2-2;
            int c=(n+t-1)/(t)*(t-1);
            vector<string> temp(r,string(c,0));
            for(int i=0,x=0,y=0;i<n;i++){
                temp[x][y]=s[i];
                if(i%t<r-1){
                    x++;
                }else{
                    x--;
                    y++;
                }
            }
            string res;
            for(auto &row:temp){
                for(char ch:row){
                    if(ch){
                        res+=ch;
                    }
                }
            }
            return res;
        }
    };
    
    
    

猜你喜欢

转载自blog.csdn.net/weixin_43424450/article/details/134367831