day5
50.第一个只出现一次的字符(重点)
思路:哈希表
遍历两次字符串s,第一遍统计各个字符的出现次数,第二遍找出第一个次数为1的字符。
ps,不要去遍历hashmap,因为map的顺序未知..自己做的时候建了两个map..
class Solution {
public:
char firstUniqChar(string s) {
unordered_map hashmap;
for(int i=0;i
由于本题只需要一个简单的哈希表就可以满足要求,我们可以建立一个长度为26的数组来构建哈希表(事实上char类型也只有256种可能)。
时间复杂度o(n)
空间复杂度o(1) :因为数组的大小是一个常数
比上面的代码更快,用的空间更小。
class Solution {
public:
char firstUniqChar(string s) {
int hashmap[26]={};
for(int i=0;i
总结:如果需要判断多个字符是不是在某个字符串里出现过或者统计多个字符在某个字符串中出现的次数,那么我们可以考虑基于数组创建一个简单的哈希表,这样可以用很小的空间消耗换来时间效率的提升。
相关题目:课本p246
50 - II .字符流中只出现一次的字符(重点)
题目:实现一个函数,用来找出字符流中第一个只出现一次的字符。例如当从字符流中只读出前两个字符“go”,第一个只出现一次的字符是“g”,当从该字符流中读出前六个字符“google”,第一个只出现一次的字符就是“l”。
思路:p247-248
以前的题目可以对str(串)进行操作,现在不存在这么一个str。
流:字符流没有存下来,无法对其进行遍历,因此在本题中,只能在数据容器哈希表中遍历,而且哈希表中存放的是对应字符的位置,而不是个数。
41.数据流中的中位数(重点)
思路1:建立一个vector不断存储数据流的数,每次寻找中位数时,就对vector 排序,然后取中间值。
超时
class MedianFinder {
public:
/** initialize your data structure here. */
vector nums;
MedianFinder() {
nums.clear();
}
void addNum(int num) {
nums.push_back(num);
}
double findMedian() {
sort(nums.begin(),nums.end());
if(nums.size()&1){
return 1.0*nums[nums.size()/2];
}
else{
return (nums[nums.size()/2] + nums[nums.size()/2-1])/2.0;
}
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
其实不必对数组排序也可以,但同样TLE
编辑代码
class MedianFinder {
public:
/** initialize your data structure here. */
vector nums;
MedianFinder() {
nums.clear();
}
void addNum(int num) {
nums.push_back(num);
}
double findMedian() {
int l=0,r=(int)nums.size()-1;//注意size()有坑
int mid=partition(l,r);
while(mid!=nums.size()/2){
if(mid>nums.size()/2){
r=mid-1;
mid=partition(l,r);
}
else{
l=mid+1;
mid=partition(l,r);
}
}
if(nums.size()&1){
return 1.0*nums[mid];
}
else{
return (nums[mid] + nums[mid-1])/2.0;
}
}
int partition(int l,int r){
int pivot=nums[l];
int index=l;
for(int i=l+1;i<=r;i++){
if(nums[i]<=pivot){
index++;
swap(nums[i],nums[index]);
}
}
swap(nums[l],nums[index]);
return index;
}
};
思路2:
受到40题的启发:”当需要在某个数据容器内频繁查找及替换最大值时,我们要想到二叉树是一个合适的选择,并能想到用堆或者红黑树等特殊的二叉树来实现“
我希望每加入一个数就能调整数据容器的结构,使得很容易取到中间值。
- multiset & map
class MedianFinder {
public:
/** initialize your data structure here. */
multiset nums;
MedianFinder() {
nums.clear();
}
void addNum(int num) {
nums.insert(num);
}
double findMedian() {
int index=0;
multiset::iterator it=nums.begin();
//size()的返回值是unsigned类型
//永远的坑 当是0之后 如果0-1 答案并不是 -1
//所以最好不要对他进行减法,或者可以强制转int
while(index < nums.size()/2 ){
index++;
it++;
}
//while过后,it指向nums.size()/2,index为nums.size()/2;
if(nums.size()&1){
return *it;
}
else{
return (*it + *(--it))/2.0;
}
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
map没写
- 堆/优先队列
没写
上面没写是因为set表现极差,说明我的思路不好。
思路3:
42.连续子数组的最大和(重点)
思路:动态规划
例如,输入的数组为{1, -2, 3, 10, -4, 7, 2, -5},和最大的子数组为{3, 10, -4, 7, 2}。因此输出为该子数组的和18 。
我们试着从头到尾逐个累加示例数组中的每个数字。初始化和为0。第一步加上第一个数字1, 此时和为1。接下来第二步加上数字-2,和就变成了-1。第三步刷上数字3。我们注意到由于此前累计的和是-1 ,小于0,那如果用-1 加上3 ,得到的和是2 , 比3 本身还小。也就是说,从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和。因此我们不用考虑从第一个数字开始的子数组,之前累计的和也被抛弃。
我们从第三个数字重新开始累加,此时得到的和是3 。接下来第四步加10,得到和为13 。第五步加上-4, 和为9。我们发现由于-4 是一个负数,因此累加-4 之后得到的和比原来的和还要小。因此我们要把之前得到的和13 保存下来,它有可能是最大的子数组的和。第六步加上数字.7,9 加7 的结果是16,此时和比之前最大的和13 还要大,把最大的子数组的和由13更新为16。第七步加上2,累加得到的和为18,同时我们也要更新最大子数组的和。第八步加上最后一个数字-5,由于得到的和为13 ,小于此前最大的和18,因此最终最大的子数组的和为18 ,对应的子数组是{3, 10, -4, 7, 2}。
如果用函数f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求出max(f(i)),其中0<=i ps:最大的子数组和并不是f(n),即以最后一个数字结尾的子数组的最大和 代码1:不额外建立数组保存f(i) 代码2:其实用两个变量CurSum MaxSum,即可。 时间复杂度o(n) 空间复杂度o(1) 不会写 思路2:课本p222-224 思路3. 思路4: 感觉这几个题解也不错,但是没精力看了,这题太难了。。 思路1: 思路2: 代码写不出来,纠结了半天,是错的,最后仿照课本写 之前一直纠结下标从0开始,但是或许不用纠结。因为虽然从0开始计数,但算第0位。 别人写的 思路也挺清晰 思路1:求出数组中所有数字的全排列,然后把每个排列拼起来,最后求出拼起来的数字的最小值。求数组的排列类似于38题:字符串的排列 思路2:课本227-230 本题考点:从问题中分析出递归的表达式,并且能够优化递归代码,用基于循环的代码来避免不必要的重复计算。 重点:定义函数f(i)表示从第i位数字开始的不同翻译的数目, 但上述递归很明显存在重复计算,具体分析如下:以12258为例。如前所述,翻译12258可以分解成两个子问题:翻译1和2558。接下来我们翻译第一个子问题中剩下的2558,同样也可以分解成两个子问题:翻译2和258,以及翻译22和58.注意到子问题翻译258重复出现了 。 递归从最大的问题开始自上而下解决问题。我们也可以从最小的子问题开始自下而上解决问题,这样就可以消除重复的子问题。也就是说,我们从数字的末尾开始,然后从右到左翻译并计算不同翻译的数目。 自己写的递归 按照上述的优化递归思路写的代码 不使用数组 课本的代码看了,嗯不知道写得如何,只是看了看,在p232 评论区也没看 思路:动态规划 优化:使用一维数组存储上一行的值即可,前几行的值已经没有利用价值 思路1:动态规划 该题可以以一个变量替代f数组,因为我们只用到了f(i-1)来计算f(i) 我们还需要创建一个数组来存储每个字符上次出现在字符串中位置的下标,我用了map。 如果只有26个字母,可以创建一个长度为26的数组,该数组所有元素的值都被初始化为-1,表示该元素还没有出现过。 思路2:滑动窗口(重点) 评论区用set\双端队列\数组来维护窗口 题意:简单来说就是,只包含 因子2、3和5 的数称作丑数。 相关概念: 不会写,知道丑数应该是因子2、3、5的累乘,可不知道怎么按顺序的求出丑数。 思路1:逐个判断每个整数是不是丑数 思路2:创建数组保存已经找到的丑数,用空间换时间 计算过程中可能会溢出,虽然溢出的数字不会存入UglyNum数组中,但是他是计算过程中产生的结果,会溢出,导致程序崩溃。 精简一下代码 思路3:对思路2进行优化 思路3总结!!: https://leetcode-cn.com/problems/chou-shu-lcof/solution/chou-shu-ii-qing-xi-de-tui-dao-si-lu-by-mrsate/ while和if都可以 思路1:暴力解 TLE 思路2:
可以将原数组 nums 用作 dp 列表,即直接在 nums 上修改即可。
由于省去 dp 列表使用的额外空间,因此空间复杂度从 O(N) 降至O(1)class Solution {
public:
int maxSubArray(vector
有的时候,题目要求可能不能修改原有数组,考虑到在dp列表中,dp[i]只和dp[i-1]有关,所以用两个参数存储循环过程中的dp[i]和dp[i-1]的值即可.class Solution {
public:
int maxSubArray(vector
ps:获取vector的最后一个元素
43. 1~n 整数中 1 出现的次数(重点)
思路1:遍历每个数,计算其中1的个数。
TLEclass Solution {
public:
int countDigitOne(int n) {
int ans=0;
for(int i=1;i<=n;i++){
ans+=countOne(i);
}
return ans;
}
int countOne(int n){
int sum=0;
while(n!=0){
if(n%10==1) sum++;
n/=10;
}
return sum;
}
};
看不懂..
https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/javadi-gui-by-xujunyi/
找规律
11.85% 50%class Solution {
public:
int countDigitOne(int n) {
string s=to_string(n);
return f(n);
}
int f(int n){
if(n<=0) return 0;
string s=to_string(n);
int high = s[0]-'0';
int poww = pow(10, s.size()-1);
int last = n - high*poww;
if (high == 1) {
return f(poww-1) + last + 1 + f(last);
} else {
return poww + high*f(poww-1) + f(last);
}
}
};
https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/shu-wei-dp-by-xun-zhao-liu-xing-luo-np70/
找规律得到递推公式
https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/zi-jie-ti-ku-jian-43-kun-nan-1n-zheng-shu-zhong-1-/
https://leetcode-cn.com/problems/number-of-digit-one/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-50/
https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/c-cong-ge-wei-bian-li-dao-zui-gao-wei-yi-ci-qiu-ji/
https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/cong-di-wei-dao-gao-wei-cong-gao-wei-dao-esqj/44.数字序列中某一位的数字(重点)
很清晰:最直观的方法就是从0开始逐一枚举每个数字。每枚举一个数字,就求出该数字是几位数,并把该数字的位数和前面所有数字的位数累加。如果位数之和仍然小于或者等于输入n,则继续枚举下一个数字。当累加的数位大于n时,那么第n位数字一定在这个数字里,再找出对应的那一位。
TLE
自己的代码不如上面的思路清晰,但本质是一样的;
为何自己的思路总是不清晰?
写代码之前先想清楚class Solution {
public:
int findNthDigit(int n) {
int ans=0;
for(int i=1;n>0;i++){
int len_i=0;
vector
思考还有没有更快的方法?我们是不是可以找出某些规律从而跳过若干数字?
以求序列中1001位为例:
序列的前10位是0~9,所以第1001位一定在10之后,因此这10个数可以直接跳过。我们再从后面紧跟的序列中找到991(991=1001-10)位的数字。
接下来180!!位数字是10~99的两位数。由于991>180,所以第991位所有的两位数之后。我们再跳过90个两位数,继续从后面找881(881=991-180)位。
接下来的2700位是900个100~999的三位数中的一位。由于881<2700,所以第881位是某个三位数中的一位。由于881=270+1,这意味着第881位是从100开始的第270个数字370的中间位,也就是7 。class Solution {
public:
int findNthDigit(int n) {
if(n<0) return -1;
int digits=1;
while(true){
//计算digits位的数至多有多少种情况 记为numbers
int numbers=0;
if(digits==1){
numbers=10;
}
else{
numbers=9*pow(10,digits-1);
}
if(n<(long)numbers*digits){//注意溢出以及判断式的写法
//当要找的那一位数字位于某m位数之中后,接着找出那一位数字
int beginNumber=digits==1?0:pow(10,digits-1);//起始数字
int number=beginNumber+n/digits;//要找的数
int indexFromRight=digits-n%digits;
for(int i=1;i
/* 数字范围 数量 位数 占多少位
1-9 9 1 9
10-99 90 2 180
100-999 900 3 2700
1000-9999 9000 4 36000 ...
例如 2901 = 9 + 180 + 2700 + 12 即一定是4位数,第12位 n = 12;
数据为 = 1000 + (12 - 1)/ 4 = 1000 + 2 = 1002
定位1002中的位置 = (n - 1) % 4 = 3 s.charAt(3) = 2;
ps:从下标为 0 开始的序列中取第 n 个,则其下标为n - 1。
如字符串 "1002" ,如果下标从0开始,则要找的第四个字符的下标是3 。
*/
class Solution {
public int findNthDigit(int n) {
int digit = 1; // n所在数字的位数
long start = 1; // 数字范围开始的第一个数
long count = 9; // 占多少位
while(n > count){
n -= count;
digit++;
start *= 10;
count = digit * start * 9;
}
long num = start + (n - 1) / digit;
return Long.toString(num).charAt((n - 1) % digit) - '0';
}
}
https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/mian-shi-ti-44-shu-zi-xu-lie-zhong-mou-yi-wei-de-6/45.把数组排成最小的数(重点)
想得出来就见鬼了46.把数字翻译成字符串(重点)
那么 f(i)=f(i+1)+g(i,i+1) * f(i+2)
当第i位和第i+1位两位数字拼接起来的数字在10~25的范围内时(不是小于等于25即可),g(i,i+1)的值为1,否则为0;
但是递归的边界条件纠结了很久才写出来,后来是静下心来思考可能出现的最终情况:l==r、r-l==1、l>r对应的返回值,才写出来的。其实心里很没有底。class Solution {
public:
int translateNum(int num) {
string s=to_string(num);
return translateNumCore(0,s.size()-1,s);
//其实不必传s.size()-1
}
int translateNumCore(int l,int r,string &s){
if(l==r){
return 1;
}
else if(r-l==1){
if((s[l]-'0')*10+s[l+1]-'0' <= 25 && s[l]!='0'){
//仅仅写小于等于25时不够的呀
return 2;
}
}
else if(l>r) return 0;
if((s[l]-'0')*10+s[l+1]-'0' <= 25 && (s[l]-'0')*10+s[l+1]-'0' >=10){
//仅仅写小于等于25时不够的呀
return translateNumCore(l+1,r,s)+translateNumCore(l+2,r,s);
}
else{
return translateNumCore(l+1,r,s);
}
}
};
class Solution {
public:
int translateNum(int num) {
if(num<0) return 0;
string s=to_string(num);
vector
class Solution {
public:
int translateNum(int num) {
if(num<0) return 0;
string s=to_string(num);
if(s.size()==1) return 1;
int f1=1,f2;
int temp=(s[s.size()-2]-'0')*10+s[s.size()-1]-'0';
if(temp<=25 && temp >=10){
f2=2;
}
else{
f2=1;
}
int f3=f2;//size等于2的情况下,没有进入循环, 但是下面返回f3
for(int i=s.size()-3;i>=0;i--){
int temp=(s[i]-'0')*10+s[i+1]-'0';
if(temp<=25 && temp >=10){
f3=f2+f1;
}
else{
f3=f2;
}
int temp2=f2;
f2=f3;
f1=temp2;
}
return f3;
}
};
可以看看别人写的递归代码 别人的题解 以及评论区
今天这题搞太久了 下次再刷
当时自己写真的写了很久
https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/solution/shou-hui-tu-jie-dfsdi-gui-ji-yi-hua-di-gui-dong-ta/
https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/solution/mian-shi-ti-46-ba-shu-zi-fan-yi-cheng-zi-fu-chua-6/
https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/solution/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-by-leetcode-sol/
https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/solution/dong-tai-gui-hua-dp-by-z1m/47.礼物的最大价值
转移方程: dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];class Solution {
public:
int maxValue(vector
class Solution {
public:
int maxValue(vector
class Solution {
public:
int maxValue(vector
48.最长不含重复字符的子字符串(重点)
定义函数f(i)表示以第i个字符为结尾的不包含重复字符的子字符串的最长长度。从左到右逐一扫描字符串中的每个字符,计算f(i)
f(i)=f(i-1)+1;
d=第i个字符和它上次出现在字符串的位置的距离
接下来分两种情况讨论
1:d<=f(i-1)
此时第i个字符上次出现在f(i-1)对应的最长子字符串之中
f(i)=d
2:d>f(i-1)
f(i)=f(i-1)+1
不应该是0,因为0表示第一个字符。不然aaaa这种,你下面一直满足==0(判断是否出现过),cur会不断增加。或者说,如果你初始化为0,那么下标应该从1开始。class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map
关键点在于:题目中要求答案必须是 子串 的长度,意味着子串内的字符在原字符串中一定是连续的。因此我们可以将答案看作原字符串的一个滑动窗口,并维护窗口内不能有重复字符,同时更新窗口的最大值。
https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/solution/tu-jie-hua-dong-chuang-kou-shuang-zhi-zhen-shi-xia/
没有实现
没有细看49.丑数(重点)
课本p240-241class Solution {
public:
int nthUglyNumber(int n) {
int number=0;
while(n){
number++;
if(IsUgly(number)){
n--;
}
}
return number;
}
//0不能进入这里判断,否则无限循环
bool IsUgly(int num){
while(num%2==0){
num/=2;
}
while(num%3==0){
num/=3;
}
while(num%5==0){
num/=5;
}
return (num==1)?true:false;
}
};
p241class Solution {
public:
int nthUglyNumber(int n) {
//vector
class Solution {
public:
int nthUglyNumber(int n) {
//vector
p241-242
代码怎么也写不出来,因为不够理解课本上的思路
最后按着课本的代码,一行行敲。
这个题用三指针,第一个丑数是1,以后的丑数都是基于前面的小丑数分别乘2,3,5构成的。我们每次添加进去一个当前计算出来个三个丑数的最小的一个,并且是谁计算的,谁指针就后移一位。class Solution {
public:
int nthUglyNumber(int n) {
vector
class Solution {
public:
int nthUglyNumber(int n) {
vector
51.数组中的逆序对
时间复杂度o(n^2)