zoukankan      html  css  js  c++  java
  • LeetCode刷题记录

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

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

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

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

    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    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)) ,后来学习了hash_map,可以利用hash—map来记录每个字符串出现的位置,这样就不需要对滑动窗口的每个元素进行遍历了,而且hash_map的复杂度是(O(1)),所以最后的复杂度是 (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(0)=a)(F(1)=b),因为到了这里我们就不能继续利用上面的地推公式了,要在此收敛。当然我们可以正着推,也可以反过来递归求解,但是递归求解相当于对一颗二叉树的所有节点进行求解,故时间复杂度是(O(2^n)),所以一般从边界条件开始正着推,此时也可以利用备忘录算法,把已经得到的数值放到map或者数组里,避免重复求解。

    这个题目里我利用了mark[i][j]这个数组(我们只利用 i < j 的这一部分),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数组,以确保递推时候的正确性,图示如下:

    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;
        }
    };
    

    12.整数转罗马数字

    罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

    字符 数值
    I 1
    V 5
    X 10
    L 50
    C 100
    D 500
    M 1000
    例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。

    通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

    I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
    X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
    C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
    给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。

    • 示例 3:
      输入: 9
      输出: "IX"

    • 示例 4:
      输入: 58
      输出: "LVIII"
      解释: L = 50, V = 5, III = 3.

    • 示例 5:
      输入: 1994
      输出: "MCMXCIV"
      解释: M = 1000, CM = 900, XC = 90, IV = 4.

    这个题目拿过来第一反应是要把每个位数分离出来,随后转换成罗马数字,再拼接到一起。中间利用了map:

    sclass Solution {
    public:
       string intToRoman(int num) {
           string  ans = "";
           int t = 10;
           int temp = 0;
           
           while( num != 0  ) {
               temp = num % t;
               num -= temp;
               t *= 10;
              ans = getOneRoman(temp) + ans;
           }
           return ans; 
       }
       
       string getOneRoman(int num){
           map<int,string> mapRom = {{1,"I"},{4,"IV"},{5,"V"},{9,"IX"},{10,"X"},{40,"XL"},{50,"L"}, {90,"XC"},{100,"C"},  {400,"CD"},{500,"D"},{900,"CM"}, {1000,"M"} };
           map<int,string>::reverse_iterator  it;
           string OneStr = "";
           while(num != 0){
               for(it = mapRom.rbegin(); it != mapRom.rend(); it++ ) {//从后向前遍历
                   if(it->first <= num ){
                       num -= it->first;
                       OneStr += it->second;
                       break;
                   }
               }
           }
           return OneStr;
       }
    };
    
    

    提交之后意识到时间复杂度惨不忍睹(一个数字长度是n,那么这个方式(n^k))。。。。。。。
    其实何必把每个位数单独分离出来呢?我们从大向小遍历,转换一个位数后,把基数减去相应的位数,继续在map中寻找。这样在map中也只需要遍历一遍。

    
    class Solution {
    public:
       string intToRoman(int num) {
           string  ans = "";
           vector<int> values={1000,900,500,400,100,90,50,40,10,9,5,4,1}; 
           vector<string> strs={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
           int i = 0;
           while( num != 0 || i > values.size() ){
               if(  num >= values[i]){
                   num -= values[i];
                   ans += strs[i];
               }else
                   i++;
           }
           return ans; 
       }
    };
    
    
  • 相关阅读:
    Git -- 撤销修改
    Git -- 管理修改
    Git -- 相关命令
    Git -- 工作区 和 暂存区
    Git -- 基本操作 之 版本回退
    Git -- 创建版本库
    Git -- 安装
    Git -- 简介
    word文档下划线无法显示的解决方法
    The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread's user context,
  • 原文地址:https://www.cnblogs.com/JeasonIsCoding/p/11626836.html
Copyright © 2011-2022 走看看