立志刷500道leetcode…每天更新…
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
拿到这个题目首先的想法是建立一个滑动窗口,利用两个变量来维护,之后对字符串进行遍历,一边遍历一边对滑动窗口内的所有字符进行检测是否重复,如果没有重复则窗口右侧增加一位,如果有重复,则滑动窗口左侧指针更新为窗口内重复元素的位置+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;
}
};
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
这个题目,也很简单,分两种情况依次遍历,分别找出奇数和偶数长度的子串就可以了,注意数组越界问题(找了挺长时间哪里数组越界,看来对编程还是不敏感),此处在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(n−1)+F(n−2),其中 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
数组,以确保递推时候的正确性,图示如下:
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);
}
};
实际提交后,会发现这种算法的时间复杂度实际很不理想,这和遍历数组的方式有关。数组在计算机中是按行线性排列的,而且计算机在从内存或缓存加载数据的时候,会默认把当前访问的数据以及其附近的多组数据一同加载,这样可以提高下次访问的速度(计算机会猜测你下次极有可能访问临近的元素),然后我们的这种访问方式就使得计算机的这种优化无用了,所以我们每次访问数组中的元素,计算机都会从新加载数据,进而时间复杂度极其不理想。
刚才上网看还有一个马拉车算法,今天还有事情,明天看一看回来更!码住
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 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);
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;
}
};