LeetCode刷题总结(二)4-6

本文详细解析LeetCode的三道算法题,包括寻找两个有序数组的中位数,通过暴力枚举找最长回文子串,以及实现Z字形变换。对于寻找中位数的问题,提出了不排序求解的思路,利用递归减少查找范围。最长回文子串的解法是中心扩散法,区分奇偶情况。Z字形变换则通过数字规律或图形思想来解决。
摘要由CSDN通过智能技术生成

(1)LeetCode4:寻找两个正序数组的中位数

两个有序数组,要求这两个数组合并之后(也是排好序的)的中位数(如果总个数为偶数,则去中间两个数的平均值;为奇数则取中间的数)。
对这个问题的解决,最简单的做法就是把两个数组合并后排序,但这样的时间复杂度较高,所以不采用!
我们可以在不对其进行排序的状态下找到中位数,首先将问题转化为这两个数组合并后从小到大排,求第k个数等于多少? 到时候要求中位数时,只需令 K = (m+n)/2即可,m,n分别为两个数组的元素个数。
○ 求第k个数的基本思路是:
①.首先,我们不合并,分别找到这A,B两个数组中索引值为( k/2)的元素,并记为k1,k2
②.k1和k2的大小比较有三种情况:
1)k1 < k2
2)k1 > k2 ,小的元素所在的数组的前(k/2)的部分一定不会有合并之后的第k个数,它们一定排在第k个数的前面,所以在考虑第k个数的位置时不用再考虑它们了,可以删掉它们
3)k1 = k2 ,两值说明两个(k/2)位置的数都可以作为第k个数,所以这里我们可以随便删掉一边,也就删掉了(k/2)个数
③.综合起来,我们每次都能删掉(k/2)个数,这样在总的数组中原来k的位置就变成了第(k/2)的位置,这样就形成了一个递归,每次我们都能够不断缩小范围,知道原来第k个位置的元素前面的元素全都被“删”完了,最后它 k -> 1时就找到了(即k在删完后变成了数组上的第一个元素),递归结束!
【注】:
1.每次新形成的数组的长度都是大的数据+小的数组减去(k/2)
2.每次砍掉的(k/2)的长度实际上都是在变化的,k = k/2,k在不断地缩小,因为原来的第k个元素在新数组中已经成为了第(k/2)个,所以每次砍掉的距离也都缩小了,在不断地向k所在位置逼近。
○ 由于每次都看去(k/2),所以该算法的时间复杂度就是log(k/2),即为1/2 log(m+n) ====》 log(m+n)

代码展示:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
      int tot = nums1.size() + nums2.size(); // 新数组的元素总数
      if(tot % 2 == 0) { 
      	// 如果总数为偶数的话中位数就是中间两个数的平均数
        int left = find (nums1, 0, nums2, 0, tot / 2); 
        int right = find (nums1, 0, nums2, 0, tot / 2 + 1); // 通过能够根据索引找到任意元素的find函数找到相应位置的值
        return (left + right) / 2.0; // 这里必须除以2.0,否则相除会出现结果截断为0的情况
      } else{ // 奇数的话就直接返回中间的元素
        return find(nums1, 0, nums2, 0, tot / 2 + 1);
      }
    }

    int find(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
        // 定义本算法中最核心的算法,通过索引位置寻找相应的元素;其中i,j分别为新数组的起点
        if(nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k); // 优化一下:我们现在只考虑一种情况,默认nums1更小,如果实际上更大,就将其与nums2的位置替换一下(且每砍一次都会比较替换一次)
        if (nums1.size() == i) return nums2[j + k - 1]; // 边界情况,如果nums1足够小,只有一个元素,那么k就可以在nums2数组中直接找到,只需去掉一个nums1元素即可
        if (k == 1) return min(nums1[i], nums2[j]); // 这是递归的终止条件,当k被砍到为1的时候,就结束递归
        
        // 正式处理逻辑
        // 确定起点;在nums1数组中(k/2)的位置确定比较特殊,因为可能它的总长度都没有(k/2),可以全部被砍掉
        // nums2数组中(k/2)的位置即为:起点+(k/2)
        int si = min(i + k / 2, int(nums1.size())), 
            sj = j + k / 2 ;  // 数组的起点从0开始,所以si,sj是k/2位置的后一个元素,砍掉之后新数组从它开始
         if(nums1[si - 1] > nums2[sj - 1]) { 
         // nums2的(k/2)的位置值小,所以要把它砍掉
            // 砍掉在代码上体现为:递归时的开始位置发生了改变
            return find(nums1, i, nums2, sj, k - (sj - j)); 
            // sj - j 表示砍掉的数的个数
         } else {   
         	// 否则就是两个数组中(k/2)位置的数nums1 <= nums2
            return find(nums1, si, nums2, j, k - (si - i));
         }
        
    }
};

【小结】
1、本题最重要的思路之一就是将寻找中位数的一部分方法抽象出来,即:根据索引寻找数组中的第k个数的值

(2)LeetCode5:最长回文子串

本题总体思路采用暴力枚举,对于每一个字符,都以之作为中间元素往左右寻找。选定中心点后,分奇偶,如果回文串为奇数个,那么中心元素不需要有对称相同的元素;如果是偶数个,那么每个对应的对称元素都要相同,对每个字符都把其考虑一下奇偶情况,最后选出最长的回文串。
假设把i作为中心点,那么:
①.偶数:
起点: i , 终点:i + 1
②.奇数:
起点:i - 1 ,终点:i + 1

代码展示:

class Solution {
public:
    string longestPalindrome(string s) {
        string res;
        for(int i = 0; i < s.size(); i ++) {
            // 奇数
            int l = i - 1,
                r = i + 1;
            while (l >= 0 && r < s.size() && s[l] == s[r]) l --, r ++; // 当两者不相等的时候,循环结束
            // 由于是while循环,所以指针会到下一个不符合条件的索引时才跳出循环
            if(res.size() < ((r - 1) - (l + 1) + 1)) res = s.substr(l + 1, ((r - 1) - (l + 1) + 1));
            //如果目标数组小于新的回文数组,那么就取新的部分

            // 偶数
            l = i;
            r = i + 1;
            while (l >= 0 && r < s.size() && s[l] == s[r]) l --, r ++; // 当两者不相等的时候,循环结束
            // 由于是while循环,所以指针会到下一个不符合条件的索引时才跳出循环
            if(res.size() < ((r - 1) - (l + 1) + 1)) res = s.substr(l + 1, ((r - 1) - (l + 1) + 1));
        }
        return res;
    }
};

【小结】这题主要在如何分奇偶上,在索引上如何分奇偶呢?偶数数在于以中心点为原点,向前移动,后一个数向后一直移动,这样能够保证所有数都遍历到,包括中心店;奇数则是不要管中心点,每次在外循环管中心点即可

(3)LeetCode6:Z字形变换

这类似于对一个字符串进行加密,不过它的排列不是一个标准的Z字形,类似这样:在这里插入图片描述
最后读取字符加密的时候是按照S形去读的。本题可以用几何的图形思想去考虑,定义上下/左右两个方向变量,让它按照题目所给的移动规律去移动,然后最后去读一下生成的图形的加密字符串;当然,我们也可以根据某种数字规律来寻找最后的结果。
○ 数字规律:
对于行数是 n 的情况:
①.对于第一行和最后一行,是公差为 2(n−1) 的等差数列,首项是 0 (第一行)和 n−1(最后一行);
②.对于第 i 行(0<i<n−1,即中间行的元素),是两个公差为 2(n−1) 的等差数列交替排列,首项分别是 i 和 2n−i−2;
○ 所以对于同一个循环,我们要分情况讨论:

代码展示:

class Solution {
public:
    string convert(string s, int numRows) {
       string res; // 最终的目标字符串
       int m = (numRows-1) * 2; // 这是公差,单独提出来
       if (numRows == 1) return s; // 极端情况,无法区分顶端行与中间行,只有一行元素
       for (int i = 0; i < numRows; i ++) { // i是用来控制行数的
           if (i == 0 || i == numRows - 1) { // 如果是第一行或最后一行
               for (int j = i; j < s.size(); j += m)  // 这是对这一行的元素进行遍历
                    res += s[j]; // 将相应的内容加到字符串的末尾,由于最后读的时候是一行一行读的
           } else {
                for (int k = i, j = numRows * 2 - 1 - i - 1;
                        j < s.size() || k < s.size();
                        j += m, k += m) { // 中间元素的首项有两种情况,把它们放到同一个循环中去遍历
                    if (k < s.size()) res += s[k]; // k循环完就循环j,交替进行
                    if (j < s.size()) res += s[j];
                }
           }
       }
       return res;                                                                    
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值