LeetCode刷题日记-持续更新中

立志刷500道leetcode…每天更新…

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

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

  • 示例 1:
    输入: “abcabcbb”
    输出: 3
    解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
  • 示例 2:
    输入: “bbbbb”
    输出: 1
    解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
  • 示例 3:
    输入: “pwwkew”
    输出: 3
    解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。

拿到这个题目首先的想法是建立一个滑动窗口,利用两个变量来维护,之后对字符串进行遍历,一边遍历一边对滑动窗口内的所有字符进行检测是否重复,如果没有重复则窗口右侧增加一位,如果有重复,则滑动窗口左侧指针更新为窗口内重复元素的位置+1。代码如下:

#include 
#include 
#include 
using namespace std;

int main(int argc, char const *argv[])
{
	string s = "pwwkew";  //样例信息
    int l, ans;      //利用l和i维持一个滑动窗口,窗口范围是[l,i)
    l = 0;    
    ans = 0;
    string s_temp;

	for (int i = 0; i < s.length(); ++i)
	{
		s_temp = s.substr(l,i-l);    // s.substr(pos,n);  从第pos个位置开始(包括pos),返回n个字符;
    	for(int j = 0; j < s_temp.length(); j++){    //从滑动窗口内开始遍历,如果串口内存在与正在扫描的第i个字符相同,则改变l指针
    		if( (s_temp[j] == s[i])  && (i != 0) ){
    			l = l + j + 1;                //把l指针位置改变为:在窗口内发现的重复字符的后一个。
    		}
    	}
    	if(  (i - l + 1) >= ans ){         //更新最大滑动窗口长度;
    		ans  =  (i - l + 1);
    	}
    }
};
	}
      
    cout << ans << endl;
	return 0;
}

提交结果时间复杂度太高 O ( n 2 ) O(n^2) O(n2) ,后来学习了hash_map,可以利用hash—map来记录每个字符串出现的位置,这样就不需要对滑动窗口的每个元素进行遍历了,而且hash_map的复杂度是 O ( 1 ) O(1) O(1),所以最后的复杂度是 O ( n ) O(n) O(n) 。代码更改如下:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int res = 0, left = -1, n = s.size();
        unordered_map<int, int> m;
        for (int i = 0; i < n; ++i) {
            if (m.count(s[i]) && m[s[i]] > left) 
                left = m[s[i]];  
            m[s[i]] = i;
            res = max(res, i - left);            
        }
        return res;
    }
};

5.最长回文子串

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

  • 示例 1:
    输入: “babad”
    输出: “bab”
    注意: “aba” 也是一个有效答案。
  • 示例 2:
    输入: “cbbd”
    输出: “bb”

这个题目,也很简单,分两种情况依次遍历,分别找出奇数和偶数长度的子串就可以了,注意数组越界问题(找了挺长时间哪里数组越界,看来对编程还是不敏感),此处在search函数里left和right指针在进行判断之前,已经是不符合判断条件的状况了,所以我们如果后续会利用这两个变量,需要对其进行复原,即left++right--(就是这里找了好久,(>_<)),代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.length();
        if(n< 2)
            return s;
        int start = 0;
        int maxLen = 0;
        for(int i = 0; i < s.length();i++)  //寻找长度为奇数
            search(s, i,  i,  start, maxLen);
        for(int i = 0; i < s.length()-1;i++)  //寻找长度为偶数
            search(s, i, i+1, start, maxLen);
        return s.substr(start, maxLen);
    }
    
    void search(string s,int left, int right, int& start,int& maxLen){
        while( (left >= 0) && (right < s.length()) && (s[left] == s[right])    ){
            left--;
            right++;
        }
        left++;
        right--;
        if( (right - left + 1) > maxLen  ){
            maxLen = right - left + 1;
            start = left;
        }
    }
};

今天尝试了用动态规划的方法去解题,动态规划是一个很像分治法的算法,都是“大事化小小事化了”的思想。动态规划实际上就是依次推这个公式: F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n-1)+F(n-2) F(n)=F(n1)+F(n2),其中 F ( n ) F(n) F(n)使我们想要的最终状态,当然我们需要边界条件,就是 F ( 0 ) = a F(0)=a F(0)=a F ( 1 ) = b F(1)=b F(1)=b,因为到了这里我们就不能继续利用上面的地推公式了,要在此收敛。当然我们可以正着推,也可以反过来递归求解,但是递归求解相当于对一颗二叉树的所有节点进行求解,故时间复杂度是 O ( 2 n ) O(2^n) O(2n),所以一般从边界条件开始正着推,此时也可以利用备忘录算法,把已经得到的数值放到map或者数组里,避免重复求解。

这个题目里我利用了mark[i][j]这个数组(我们只利用 i < j 的这一部分),i就是子串的左边界,j就是子串的右边界,如果 s [ i , j ] s[i,j] s[i,j]这个子串是一个回文子串那么mark[i][j] = 1。判断其是否是回文子串的条件是动态规划算法的核心if( (s[i] == s[j]) && ( j-i<2 || mark[i+1][j-1] ) ),其边界条件是mark[i][i]=1。除此之外,需要注意的是,因为我们是从边界条件开始递推所有数组的状态,所以我们需要沿着对角线方向依次向上遍历mark数组,以确保递推时候的正确性,图示如下:

LeetCode刷题日记-持续更新中_第1张图片
class Solution {
public:
    string longestPalindrome(string s) {
        int start = 0;
        int n = s.length();
        if (n < 2 )
            return s;
        int mark[n][n] = {0};
        for(int i = 0; i < n ;i++)   //初始化数组
            for(int j = 0;j < n;j++)
                mark[i][j]=0;
        int MaxLen = 0;
        int left = 0;
        
        for(int k = 0; k < n; k++){
            for(int i = 0,j = k; i < n - k; i++,j++){          //确保沿对角线向数组右上方遍历;
                mark[i][i] = 1;
                if( (s[i] == s[j])  && ( ( j-i<2 ) || mark[i+1][j-1] ) )
                    mark[i][j] = 1;
                if( mark[i][j] && (j-i+1 > MaxLen) ) {
                    MaxLen = j-i+1;
                    start = i;
                }
            }
        }
        return s.substr(start,MaxLen);
    }
};

实际提交后,会发现这种算法的时间复杂度实际很不理想,这和遍历数组的方式有关。数组在计算机中是按行线性排列的,而且计算机在从内存或缓存加载数据的时候,会默认把当前访问的数据以及其附近的多组数据一同加载,这样可以提高下次访问的速度(计算机会猜测你下次极有可能访问临近的元素),然后我们的这种访问方式就使得计算机的这种优化无用了,所以我们每次访问数组中的元素,计算机都会从新加载数据,进而时间复杂度极其不理想。
刚才上网看还有一个马拉车算法,今天还有事情,明天看一看回来更!码住

6.Z字形变换

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

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

L   C   I   R
E T O E S I I G
E   D   H   N

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

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

string convert(string s, int numRows);

  • 示例 1:
    输入: s = “LEETCODEISHIRING”, numRows = 3
    输出: “LCIRETOESIIGEDHN”
  • 示例 2:
    输入: s = “LEETCODEISHIRING”, numRows = 4
    输出: “LDREOEIIECIHNTSG”
    解释:
L     D     R
E   O E   I I
E C   I H   N
T     S     G

这个题目比较简单,很容易发现规律:对于每n排竖直排列的字母,相差的索引都是size = 2 * ,numRows - 2个。随后我们判断是否是首行或者尾行,如果是,直接利用上面规律就好,如果不是,我们需要插入 s[j + size - 2 * i]个元素(i是当前行数,j是一个循环变量变量,j每次递增size个单位)。此外需要注意数组是否越界,和输入空字符串的情况。

class Solution {
public:
    string convert(string s, int numRows) {
        
        if (numRows <= 1) return s;
        string res = "";
        int size = 2 * numRows - 2;

        for (int i =0; i< numRows; i++){
            for(int j = i; j < s.length(); j = j + size){
                if( (i == 0)||(i==numRows-1) ){ //说明是首行或者尾行
                    res += s[j];
                }else{  //中间行
                    res += s[j];
                    if( j + size - 2 * i < s.length() ) //边界判断
                        res += s[j + size - 2 * i];
                }
            }
        }
        return res;
    }
};

你可能感兴趣的:(数据结构和算法,LeetCode)