LeetCode(4-寻找两个正序数组的中位数&&5-最长回文子串&&6-Z形变换)

简介: LeetCode(4-寻找两个正序数组的中位数&&5-最长回文子串&&6-Z形变换)

寻找两个正序数组的中位数


题目描述:


给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。

进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

示例 1:

输入:nums1 = [1,3], nums2 = [2]

输出:2.00000

解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]

输出:2.50000

解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

示例 3:

输入:nums1 = [0,0], nums2 = [0,0]

输出:0.00000

示例 4:

输入:nums1 = [], nums2 = [1]

输出:1.00000

示例 5:

输入:nums1 = [2], nums2 = []

输出:2.00000


解题思路:


首先我们需要明白一点,那就是重新组合后的数组长度为奇数或者偶数的时候,中位数的计算方式是不一样的.


如果组合之后长度是奇数,那么中位数就应该是下图所示:


如果组合之后长度是偶数,那么中位数就应该是下图所示:


20210204092354960.png


所以到最后我们肯定是要分情况讨论的.


其次如果一点都不考虑复杂度的话,我们可以直接将两个正序序列重新组合成一个正序序列,这样我们就可以我们只需要分长度是偶数还是奇数讨论即可.这个就对应我的第一版代码.


如果想要优化一下算法的话,我们其实可以想一想,既然他让我们找出两个序列的的中位数,那么很显然我们只要将两个数组组合到一半就行了,剩下的元素我们就没有必要继续继续组合了,这样 时间复杂度和空间复杂度都只有原来的一半 了.毕竟只遍历的一半的元素.对应我的第二版代码.


源代码:


这是我的第一版代码:


class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    List<Integer>list=new ArrayList<Integer>();
    int i=0;
    int j=0;
    //重新组合序列
    while(i<nums1.length&&j<nums2.length) {
        if(nums1[i]<=nums2[j]) {
          list.add(nums1[i++]);
        }
        else {
          list.add(nums2[j++]);
                }
      }
        //如果跳出上面的循环,那么显然至少有一个数组已经遍历结束了
      //那么很显然我们就只需要再吧最后剩下的数组遍历结束即可
        while(j<nums2.length) {
            list.add(nums2[j++]);
        }
        //这里理由同上
        while(i<nums1.length) {
            list.add(nums1[i++]);
        }
        //分情况返回或者中位数
    if(list.size()%2==0) {
      double result=(double)(list.get(list.size()/2-1)+list.get(list.size()/2))/2;
      return result;
    }
    else {
      return list.get(list.size()/2);
    }
    }
}


20210205092455771.png


这是我的第二版代码:

class Solution {
   public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int sum=nums1.length+nums2.length;
    //这里我们将数组的长度定义为+1,是为了包容两种情况
    //否则又得分情况讨论
    int nums3[]=new int [sum/2+1];
    int i=0,j=0,k=0;
    //重新组合序列
    while (i<nums3.length) {
      if(j<nums1.length&&k<nums2.length) {
        if(nums1[j]<nums2[k]) {
          nums3[i++]=nums1[j++];
        }
        else {
          nums3[i++]=nums2[k++];
        }
      }
            else 
        break;
    }
    //这里和第一版的代码差不多,只不过多了一步判断是否越过重新组合序列长度
        while(i<nums3.length&&j<nums1.length) {
      nums3[i++]=nums1[j++];
    }
        while(i<nums3.length&&k<nums2.length) {
            nums3[i++]=nums2[k++];
        }
        //分情况返回或者中位数
    if(sum%2==0)
      return (double)(nums3[nums3.length-2]+nums3[nums3.length-1])/2;
    else 
      return nums3[nums3.length-1];
    }
}


2021020509260233.png


虽然两者的代码看起来没有什么变化,但是大家可能测试比较一下两版的代码,会发现不管是在时间复杂度或者是空间复杂度上都有了明显的提升.


!!!最长回文子串!!!(重点掌握)


题目描述:


给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”

输出: “bab”

注意: “aba” 也是一个有效答案。

示例 2:

输入: “cbbd”

输出: “bb”


暴力求解

解题思路:


当然了不动脑子的话,很容易想到暴力求解.只需要从最长的字符串开始递归进行检查,看是不是回文串,如果是的话,那么就直接返回即可.


Up一开始就是不动脑子,直接暴力的,暴力求解思路很简单并且代码编写也简单,并且可读性也比较好.但是缺点也很明显那就是时间复杂度太高.指不定就时间就超了.


源代码:


class Solution {
    //判断是不是回文串
    public boolean panduan(String string1) {
    char[]ch=string1.toCharArray();
    for(int i=0;i<ch.length/2;i++) {
      if(ch[i]!=ch[ch.length-i-1])
        return false;
    }
    return true;
  }
  //暴力求解
  public String longestPalindrome(String s) {
    for(int i=s.length();i>0;i--) {
      for(int j=0;j<s.length()-i+1;j++) {
        if(panduan(s.substring(j, j+i))) {
          return s.substring(j, j+i);
        }
      }
    }
    return "";
   }
}

20210207092353118.png


我们可以看到值通过了不到一般的样例,所以我们必须要使用其他的方法.


动态规划


解题思路:


其实我们看过上面暴力求解的算法之后,我们就能看到,这个过程中 最耗费时间的就是判断该字符串是不是回文串,所以如果我们能够非常快速的就能判断一个字符串是不是回文串的话,那么很显然我们的时间复杂度就能够大幅度的降低了.


这里我们就来分析一下我们怎么能快速的判断一个字符串是不是回文串呢.可以看到这部分我用的是动态规划,所以关于动态规划,大家首先需要考虑的就是找到状态转移方程!!!


这里我们不妨先作出下面这两个假设:

1.假设dp[i][j]代表字符串下标从i到j的的字符串

dp[i+1][j-1]代表字符串下标从i+1到j-1的的字符串

2.假设我们已经知道了dp[i+1][j-1]是不是回文串了之后,我们如何快速的判断dp[i][j]是不是回文串呢?

根据回文串的定义,肯定是要首尾字符对应的,那么很显然我们是不是能够推导出下面状态转移方程:

dp[i][j]为回文串 等价于 dp[i+1][j-1]为回文串&&s[i]=s[j],方程图解如下图所示:


20210207093737479.png


既然我们有了这个转台转移方程之后我们就只剩下一件事了,那就是定义我们的初始状态,根据我们的状态转移方程,我们可以知道,想知道长的字符串是不是回文串就必须要知道短的字符串的状态,所以我们可以推断出,这里的初始状态应该就字符串中每一个单个字符的状态,并且单个字符我们都是看做是回文串的.那么很显然我们的转台转移返程以及初始状态我们都已经找到了.那么接下来我们就可以编写我们的代码了.


源代码:

class Solution {
 public String longestPalindrome(String s) {
    boolean [][]dp=new boolean[s.length()][s.length()];
    char []schar=s.toCharArray();
    int max=Integer.MIN_VALUE;
    int begin=0;
    int end=0;
    for(int i=0;i<schar.length;i++) {
      dp[i][i]=true;
    }
    //k为步长,i为起点,j为终点
    for(int k=1;k<schar.length;k++) {
      for(int i=0;i<schar.length-k;i++) {
        int j=i+k;
        //处理回文串长度为偶数的情况
        if(j-i==1) {
          if(schar[i]==schar[j]) {
            dp[i][j]=true;
            //记录最大的回文串索引
            if(j-i+1>max) {
              max=j-i+1;
              begin=i;
              end=j;
            }
          }
          else 
            dp[i][j]=false;
        }
        //防止数组越界
        else if(i+1<=j-1) {
          if(schar[i]==schar[j]&&dp[i+1][j-1]) {
            dp[i][j]=true;
            //记录最大的回文串索引
            if(j-i+1>max) {
              max=j-i+1;
              begin=i;
              end=j;
            }
          }
          else 
            dp[i][j]=false;
        } 
      }
    }
    return s.substring(begin, end+1);
    }
}

20210207100240296.png


中心扩散法


解题思路:

中心扩散法的思想就是以一个元素为中间点,然后向两边进行扩散,扩散的过程找到以该元素为中间点的最大的回文串.因为只遍历了一次字符串,所以使得时间复杂度达到了线性级别即O(n),相比我们上面的动态规划,会显得更加的快速.


但是这个扩散的过程还有这个注意点,就是那些元素可以当做是中间点呢?

这个就需要我们再次分情况讨论了:

当我们的字符串长度是偶数的时候,那么很显然中间点应该是这样的:


20210207104004166.png


中间点并不是指向一个元素的,而是一个夹缝.


但是当我们的字符串长度是奇数的时候,那么很显然中间点应该是这样的:


20210207104222857.png


这时候的中间点是指向一个元素的.


我们分析完上面两种情况之后,我们基本就能分析得到哪些位置是可以作为中间点的了,就如下图所示:


20210207105031181.png


可以看到除了队头以及队尾元素,其他的位置都是可以作为我们的中间点的.知道了上面这些概念之后,我们的思路就已经讲解完毕了.


源代码:


class Solution {
//中心扩散法
  public String longestPalindrome(String s) {
    int max=1;
    String result=s.substring(0,1);
    for(int i=0;i<s.length()-1;i++) {
        //长度为奇数的最大回文串
      String oddString=diffusion(s, i, i);
      //长度为偶数的最大回文串
      String evenString=diffusion(s, i, i+1);
      String maxString=oddString.length()>evenString.length()?oddString:evenString;
      if(maxString.length()>max) {
        max=maxString.length();
        result=maxString;
      }
    }
    return result;
    }
  //扩散函数,找到以当前位置为中间点的最大回文串
  public String diffusion(String s,int left,int right) {
    int i=left;
    int j=right;
    while(i>=0&&j<s.length()) {
      if(s.charAt(i)==s.charAt(j)) {
        i--;
        j++;
      }
      else {
        break;
      }
    }
    return s.substring(i+1, j);
  }
}

20210207100359203.png


Z形变换


题目描述:


将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:


之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

示例 1:

输入:s = “PAYPALISHIRING”, numRows = 3

输出:“PAHNAPLSIIGYIR”

示例 2:

输入:s = “PAYPALISHIRING”, numRows = 4

输出:“PINALSIGYAHRPI”

解释:


示例 3:

输入:s = “A”, numRows = 1

输出:“A”


解题思路:

这题属于看上去难,但是找到思路之后就会很简单的题.这里我们可以将整个字符串划分成一份一份的来看.假设我们的numRows为4的话,那么可以将字符串以2*numRows-2=6为一份,就如下图所示:


分成这样的一份一份的之后,我们就需要解决下面一个问题:怎么将对应的元素塞入对应的行里面的,也就是怎么快速计算每个字符对应的行号呢?


这里其实我们想想就能知道,这就是个找规律的问题,上面我们已经将字符串拆分成一份一份了,那么很显然我们就可以直接通过%(2*numRows)的方式来计算出每个字符对应的位置但是这里还需要注意一点,这个我在下面的图里面已经写出来了.即下图所示:


20210207140629751.png


这样我们的序列就能够正常的添加到相应的字符串队列中了,最后我们只需要按照顺序将我们的字符串添加起来就行了.


源代码:

class Solution {
   //判断每个字符应该添加在哪一行
   public int row(int index,int numRows) {
    if(index%(numRows*2-2)<numRows)
      return index%(numRows*2-2);
    else 
      return 2*numRows-2-index%(numRows*2-2);
  }
  public String convert(String s, int numRows) {
    if(numRows==1) {
      return s;
    }else {
        //创建numRows个字符串用来存储我们的元素
      StringBuffer []stringBuffers=new StringBuffer[numRows];
      for(int i=0;i<numRows;i++) {
        stringBuffers[i]=new StringBuffer();
      }
      //根据计算出来的行号,将元素添加到对应行号的字符串中
      for(int i=0;i<s.length();i++) {
        stringBuffers[row(i, numRows)].append(s.charAt(i));
      }
      //将之前我们已经分配好元素的字符串按照顺序进行拼接
      StringBuffer stringBuffer=new StringBuffer();
      for(int i=0;i<numRows;i++) {
        stringBuffer.append(stringBuffers[i]);
      }
      return stringBuffer.toString();
    }
    }
}

20210207105730113.png

相关文章
|
1月前
Leetcode第五题(最长回文子串)
这篇文章介绍了解决LeetCode第五题“最长回文子串”问题的一种方法,使用了中心扩展法来寻找给定字符串中的最长回文子串。
33 0
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
3月前
|
算法
LeetCode第53题最大子数组和
LeetCode第53题"最大子数组和"的解题方法,利用动态规划思想,通过一次遍历数组,维护到当前元素为止的最大子数组和,有效避免了复杂度更高的暴力解法。
LeetCode第53题最大子数组和
|
3月前
|
存储 Java API
LeetCode------合并两个有序数组(4)【数组】
这篇文章介绍了LeetCode上的"合并两个有序数组"问题,并提供了三种解法:第一种是使用Java的Arrays.sort()方法直接对合并后的数组进行排序;第二种是使用辅助数组和双指针技术进行合并;第三种则是从后向前的双指针方法,避免了使用额外的辅助数组。
LeetCode------合并两个有序数组(4)【数组】
|
3月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
113 2
LeetCode------找到所有数组中消失的数字(6)【数组】
这篇文章介绍了LeetCode上的"找到所有数组中消失的数字"问题,提供了一种解法,通过两次遍历来找出所有未在数组中出现的数字:第一次遍历将数组中的每个数字对应位置的值增加数组长度,第二次遍历找出所有未被增加的数字,即缺失的数字。
|
3月前
|
前端开发
LeetCode------移动零(5)【数组】
这篇文章介绍了LeetCode上的"移动零"问题,提出了一种使用双指针的原地操作解法,该方法首先将非零元素移动到数组前端并保持相对顺序,然后填充后续位置为零,以达到题目要求。
|
1月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
19 4
|
1月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
18 0
Leetcode第三十三题(搜索旋转排序数组)
|
1月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
57 0