leetcode

leetcode刷题之路

简介:之前刷过leetcode,但是因为决心不坚定以及一直弄不明白leetcode编辑器的原理,所以一直断断续续,现在下定决心每天两道(有可能一道,视时间而定)由简单到困难,算是为以后铺路。
⏱:指隔一段时间回来看看的感想,可能改改代码、写写心得啥的。

本文章就是为了记录刷题的心得和源代码

day1

1、两数之和

class Solution(object):
    def twoSum(self, nums, target):
       for i in range(0, len(nums)):
        for j in range(i+1, len(nums)):
            if nums[i] + nums[j] == target:
                return i,j

成绩:16 13
时间复杂度:O(n*n)
总结:
1、控制变量 j 应该从 i+1 开始,否则会遇到某元素自加为target的情况。比如测试实例中的第二个输入:

nums = [3,2,4], target = 6
correct output:[1,2]
error output:[0,0] [1,2]

2、这道题在hashmap(字典)标签下,所以可以用hashmap来解,这样的时间复杂度为O(n)

class Solution:
    def twoSum(self, nums, target):
        tmp = {}
        for k, v in enumerate(nums):
            if target - v in tmp:
                return [tmp[target - v], k]
            tmp[v] = k

enumerate函数可以理解为python中的map函数

seq = ['one', 'two', 'three']
 for i, element in enumerate(seq):
     print i, element
output:
0 one
1 two
2 three

day2

整数反转

class Solution:
    def reverse(self, x: int) -> int:
        #思路就是:反复取余10,反复加10*余数
        Sum = 0
        Temp = x
        if Temp >= 0:
            while Temp != 0:
                Sum = 10 * Sum + Temp % 10
                Temp = int(Temp / 10)
        else:
            Temp = abs(Temp)
            while Temp != 0:
                Sum = 10 * Sum + Temp % 10
                Temp = int(Temp / 10)
            Sum = -Sum
        if Sum >= -2**31 and Sum <=2**31-1:
            return Sum
        else:
            return 0

成绩:97.7 50.24
总结:
1、因为python不需要对变量进行定义,所以在整除时需要把temp/10转换成int
2、python中对复数进行整除时和c++不太一样,所以偷懒直接分情况讨论了,肯定有更好的解决方法

⏱:这写的多少有点愚蠢了。有一点就是,else能少写就少写。

class Solution {
public:
    int reverse(int x) {
        //long sign = 1;
        //if(x<0) sign = -1;
        long sum = 0;
        long dig = x;
        while(dig!=0){
            sum = sum * 10 + dig % 10;
            dig /= 10;
        }
        return sum>INT_MAX || sum<INT_MIN ? 0:sum;
    }
};

成绩:100 76.68

回文数

class Solution:
    def isPalindrome(self, x: int) -> bool:
        if x < 0:
            return False
        x_str = str(x)
        if x_str[::-1] == str(x):
            return True
        else:
            return False

成绩:99.25 20.81
总结:
这题蛮简单的,用python对字符串处理的优势,没啥难度
1、python里的bool类型返回值为True, False。注意大小写

⏱:c++比python快了不止一星半点

class Solution {
public:
    bool isPalindrome(int x) {
        if(x<0) return false;
        long dig = x;
        long sum = 0;
        while(dig!=0){
            sum = sum*10 + dig%10;
            dig = dig/10;
        }
        return x == sum ? true:false;
    }
};

⏱:leetcode官方题解是反转整数的一般进行比较,我感觉是没有什么太大的意义。

day3

罗马数字转整数

class Solution:
    def romanToInt(self, s: str) -> int:
        dict = {'I': 1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}
        sum = dict[s[-1]]
        if s == None:
            return
        for i in range(-1, -len(s), -1):
            if dict[s[i]] <= dict[s[i-1]]:
               sum += dict[s[i-1]]
            else:
                sum -= dict[s[i-1]]
        return sum

成绩: 49.84 80.49
思路:从后往前遍历罗马字符:
当前一个字符对应的数比当前字符对应的数小时,总和减去前一个字符对应的数
反之,则加上前一个字符对应的数
总结:
1、主要用到的就是python里的dict容器,用法和c++里的map容器相同,其实就是哈希表。确实是个好东西,又快又好用。
2、一开始想要两个比较完直接比较下两个,但这样一想不太对,可能会导致溢出。所以就采取把最后一个字符对应的整数作为sum的初始值,对前一个字符比较加减,这样就能解决溢出的问题。

day4

最长公共前缀

class Solution:
    def logestProfix(self, str1: str, str2: str) -> str:
        if str1 == None or str2 == None:
           return
        temp = ""  #这里不能用temp = None,原因见总结
        min_len = min(len(str1), len(str2))
        for i in range(min_len):
            if (str1[i] == str2[i]):
                temp += str1[i]
            else:
                break
        return temp

    def longestCommonPrefix(self, strs: List[str]) -> str:
        if strs == []:   #这里进行分类是必要的,原因见总结
            return ""
        str_min = strs[0]
        for i in range(1, len(strs), 1):
            str_min = self.logestProfix(str_min, strs[i])
        return str_min

成绩:90.26 28.87
总结:
1、当字符串strs数组为空时,不能使用strs[0]表示字符串数组的第一个字符串,会报错。
2、在python中将字符串定义为
None
时,不能对其做 + 操作,例如:

str = "123456"
temp = None
for i in range(len(str)):
    temp = temp + str[i]
print(temp)

报错信息:

Traceback (most recent call last):
  File "D:\APP\python\pycharm\project\lcs.py", line 4, in <module>
    temp = temp + str[i]
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

3、我的想法是横向比较,还有一种比较容易想到的方法是纵向比较。即取出第一个字符串的每一个字符,和其他字符串对应位置的字符进行比较。指导有不相同的字符串或碰到堆笑长度的字符,就直接返回第一个字符串相应长度的切片。

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if not strs:
            return ""
        
        length, count = len(strs[0]), len(strs)
        for i in range(length):
            c = strs[0][i]
            if any(i == len(strs[j]) or strs[j][i] != c for j in range(1, count)): ##当碰到最短长度字符串 or 对应位置的字符不相等
                return strs[0][:i]
        
        return strs[0]

*这里面的any函数的作用是当参数全部为False时,返回False;如果遇到True,则直接返回True。

有效的括号

原始通过代码:

class Solution:
    def isValid(self, s: str) -> bool:
        dict = {'(':')', '{':'}', '[':']'}
        if str == "":
            return False
        stack = []
        for c in s:
            if c in dict:
                stack.append(c)
            elif c in [')', '}', ']']:
                if stack == []:   ##如果读入右侧括号时栈为空,则为False
                    return False   
                elif c == dict[stack[-1]]:  ##匹配成功
                    stack.pop()
                else:                       #匹配不成功
                    return False
        if stack == []:
            return True
        else:
            return False

通过学习大佬代码后改进代码:

class Solution:
    def isValid(self, s: str) -> bool:
        dict = {'(':')', '{':'}', '[':']'}
        if str == "":
            return False
        stack = []
        for c in s:
            if c in dict:
                stack.append(c)
            elif c in [')', '}', ']']:
                if stack == []:   ##如果读入右侧括号时栈为空,则为False(意思就是一开始读入的就是右括号,则直接结束)
                    return False   
                if c != dict[stack.pop()]:  ##匹配不成功
                    return False
        return len(stack) == 0

成绩:86.34 6.03
总结:
1、主要改进点有两个:
①读入右侧括号后匹配不成功时的判断,直接pop出栈顶元素进行比较,若匹配不成功,则直接结束,栈顶元素是否还在都无所谓;若匹配成功,则也已经完成pop操作了。比我原来的先判断栈顶是否匹配,若匹配再pop要省时。该改进使得
②最后返回值的判断。最后添加的非空返回false主要是考虑到s只有一个左括号的情况。改进后就可以直接通过判断栈的长度是否是0来检验了。这个改进主要是为了代码的间接,没有本质上地提升。
2、当字符串s空时,就不能使用s[-1],和上一题的 s[0] 一样,会报错。

day5

合并两个有序链表

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next 
class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        l3 = ListNode()  #结果链表
        p3 = l3          #游标,作为尾节点
        while l1 and l2:
            if l1.val <= l2.val:
                p3.next = l1
                p3 = l1
                l1 = l1.next
            else:
                p3.next = l2
                p3 = l2
                l2 = l2.next
        if l1: p3.next = l1
        else: p3.next = l2
        return l3.next

成绩:38.94 16.49
总结:
1、算是链表非常入门的一道题了,用迭代做这道题还是非常容易理解的,构建一个结果链表l3, 通过游标来合并两个链表。若l1指向的节点数值小于l2p3就指向l1,且l1p3都指向它们的next;反之也相似。最终至多有一条链表还有剩余,则将p3指向该链表即可。最后返回结果链表l3next即可。
2、这道题还可以用递归做,还是那句话,看着递归代码觉得容易,自己想想破头都想不出来。先粘上官方的python递归,等我进化了再来看。

class Solution:
    def mergeTwoLists(self, l1, l2):
        if l1 is None:
            return l2
        elif l2 is None:
            return l1
        elif l1.val < l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2

递归得从刚出来的时候开始思考,我现在感到疑惑的是两个return是怎么让它们连接起来的,或许单纯的就是返回节点,并没有形成链表,感觉这样理解比较合适一些。希望如果有大佬看到这里,可以解答一下哦。

删除有序数组中的重复项

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if nums == []:
            return 
        temp = nums[0]
        length = len(nums)
        i=1
        while i != length:
            if temp < nums[i]:
                    temp = nums[i]
                    i += 1
            else:
                if temp == nums[i]:
                    del nums[i]
                    length -= 1
        return len(nums)

成绩:18.97 20.51(确实慢啊)
总结:
1、做这道题可谓是一波三折呀。最主要的问题就是:当删除元素时,数组长度会改变,元素的相对位置也会改变,所用我一开始用for循环来遍历数组是错误的。

def removeDuplicates(nums) -> int:
    temp = nums[0]
    length = len(nums)
    for i in range(1, length-1, 1):  ##不能这么控制
        if temp < nums[i]:
            temp = nums[i]
            i -= 1              ##即使这里i-1了,回到for循环后i照样是原来的控制量,并没有-1
        else:
            for j in range(i, length-1, 1):##这里也一样
                while temp == nums[j]:
                    del nums[j]
                    length -= 1  
                break
    return len(nums)

所以后来我改成最终提交的版本,用iwhile分情况来控制,就比较合理了,理解起来也比较容易,就是慢。
2、leetcode官方的解法我看了,其实官方解法并没有实质性地删除重复项,仅仅是把后面的非重复项往前覆盖,这样确实会好思考一点:
官方解法思路:如果快指针相邻项相等,快指针后移,慢指针项等待被覆盖;如果快指针指向的相邻项不相等,快指针指向项覆盖慢指针指向项,慢指针后移。
我理解错题目了。
⏱:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        int slow = 0;
        int fast = 1;
        while(fast<nums.size()){
            if(nums[fast-1] == nums[fast]){
                fast++;
            }
            else{
                nums[++slow] = nums[fast++];
            }
        }
        return slow+1;
    }
};

3、python中删除数组中某一元素一般有三种方法:removepop以及del。具体可以看一看这篇博文:

https://www.cnblogs.com/huangbiquan/articles/7740894.html

day6

移除元素

class Solution:

    def removeElement(self, nums: List[int], val: int) -> int:
        if not nums:
            return 0
        slow, fast = 0, 0
        while fast < len(nums):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow

成绩:62 53
总结:
1、该题也是利用双指针进行求解。需要考虑的就是两个指针什么时候需要移动以及什么时候将后面的数往前覆盖这样两个问题。
slow指针:找不到val的时候往后移动
fast指针:一直往后移动
什么时候往前覆盖:当fast找到非val元素时
将这样的思路变成代码就能很轻松地解决这道题了

day7

搜索插入位置

暴力解法,时间复杂度为O(n)

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        if not nums:
            return 0
        if nums[0] >= target:
            return 0
        i = 1
        while i < len(nums):
            if not target <= nums[i] and target > nums[i-1]:
                i += 1
            else:
                return i
        return len(nums)

成绩:84.19 77.75
二分法,时间复杂度为O(logn)

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        if not nums:
            return 0
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = int((left + right)/2)
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return left

成绩:95.28 32.72

day8

实现strStr()

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        if not needle:
            return 0
        j = 0
        needle_nextval = self.get_nextval(needle)
        for i in range(len(haystack)):
            while j > 0 and haystack[i] != needle[j]:
                j = needle_nextval[j - 1]
            if haystack[i] == needle[j]:
                j += 1
            if j == len(needle):  #到头了,找到了
                return i - len(needle) + 1
        else: return -1


    def get_nextval(self, temp: str) -> str:
        j = 0
        next = [0] * len(temp)
        for i in range(1, len(temp), 1):
            while j > 0 and temp[i] != temp[j]:
                j = next[j - 1]
            if temp[i] == temp[j]:
                j += 1
            next[i] = j
        return next 

成绩:30.08 5.05
总结:就是很典型的kmp算法,主算法非常明确统一,但是关于next数组在业界有很多不同的解法,尤其是严蔚敏的算法与数据结构书上的next算法,现在看来真是独具一格。

//补个c++的
#include
#include
#include
using namespace std;

vector<int> get_nextval(string);

int KMP(string haystack, string needle) {
	if (needle == "") {
		return 0;
	}
	int j = 0;
	vector<int> needle_nextval = get_nextval(needle);
	for (int i = 0; i < haystack.length(); i++) {
		while (j > 0 && haystack[i] != needle[j]) {
			//不匹配,回溯
			j = needle_nextval[j - 1];
		}
		if (haystack[i] == needle[j]) {
			//匹配,继续向后遍历
			j++;
		}
		if (j == needle.length()) {
			//找到了
			return i - needle.length() + 1;
		}
	}
	return 0;
}

vector<int> get_nextval(string temp) {
	int j = 0;
	int len = temp.length();
	vector<int> next(len);
	for (int i = 1; i < len; i++) {
		while (j > 0 && temp[i] != temp[j]) {
			i = next[j - 1];
		}
		if (temp[i] == temp[j]) {
			j++;
		}
		next[i] = j;
	}
	return next;
}

int main() {
	string needle; //子串
	string haystack;//母串
	//int num;//待搜索字符串数
	//cin >> needle >> num;
	//vector list(num);
	//for (int i = 0; i < num; i++) {
	//	cin >> list[i];
	//}
	//for (int i = 0; i < num; i++) {
	//	cout << KMP(list[i], needle) << endl;
	//}
	needle = "sf";
	haystack = "ssssssf";
	cout << KMP(haystack, needle);
}

day9

外观数列

class Solution:
    def countAndSay(self, n: int) -> str:
        temp = "1"
        i = 1
        while i < n:
            temp = self.group(temp)
            i += 1
        return temp

    def group(self, s):
        i, j = 0, 0
        Out = ""
        while i < len(s):
            while j < len(s) and s[i] == s[j]:
                j += 1
            Out = Out + str(j - i) + s[i]
            i = j
        return Out

成绩:53.91 79.99
思路:正如题目描述里所说,这道题最重要的是把字符串分块进行统计输出。group函数就通过双指针一前一后将字符串分块统计并返回统计信息。在主函数里迭代调用group函数,就能非常顺利地完成题目。

总结:这道题里也用了双指针的思想来遍历数组,也大概能感受出双指针的两个好处:其中之一就是控制数组元素,不论是替换元素还是分块元素,都非常方便;第二就是在做前后比较时不用管边界元素,不会有溢出的风险;还有一个就是能更灵活地进行元素比较。

day10

最大子序和

暴力法
遍历计算给定长度的字符串切片的和,不断更新最大值。结果最后直接干超时了哈哈哈,第201个输入实例有10000项,慢出天际。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if not nums:
            return 0
        if len(nums) == 1:
            return nums[0]
        max = -float('inf')
        for i in range(len(nums)):        #距离
            for j in range(len(nums)):          #遍历控制
                if j + i <= len(nums) and max < sum(nums[j : j + i + 1]):
                    max = sum(nums[j : j + i + 1])
                    
        return max

动态规划法
这道题最合理的方法应该是动态规划法,要想利用动态规划,最重要的是弄明白最大子序和的形成过程:
1、 以当前点为起始点往后加,然后找出所有有可能的子序的最大值。这种思维就是暴力解法
2、 以当前点为终止点,找出所有有可能的子序的最大值,这样就能找出子序和之间的递推关系:以当前点为终止点的子序和以当前点的前一个点为终止点的子序和有关
具体可以看这位同学的思路解析,对我理解动态规划的帮助还是蛮大的

https://leetcode-cn.com/problems/maximum-subarray/solution/xiang-xi-jie-du-dong-tai-gui-hua-de-shi-xian-yi-li/

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return nums[0]
        result = max(-float('inf'), nums[0])
        for i in range(1, len(nums), 1):
            nums[i] = max(nums[i], nums[i] + nums[i - 1])
            result = max(result, nums[i])
        return result

成绩:21.49 27.49

day11

最后一个单词的长度

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        if s.isspace():
            return 0
        x = s[::-1].split()
        for temp in x:
            if not temp.isspace():
                return len(temp)
                break

这个解法主要是运用了split函数,这种运用函数的解法我说实话不是太喜欢,直接找思路做吧。
思路:
首先我们解决这道题的核心就是从后往前遍历找空格,那么会存在以下三种情况:
1、 空格在字符中间,这是最常规的一种情况,只要遍历字符找到空格停止,return当前长度即可;
2、 字符中无空格,即字符串只有一个单词。这种情况其实和第一种没有太大的差别,就是遍历到最后停止,输出当前长度罢了;
3、 空格在字符开头,例如"a ",那么这种情况首先就要过滤开头的空格,之后就转到上面两种情况了。

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        if s.isspace():
            return 0
        count = 0
        i = 0
        while(s[::-1][i].isspace()):   #过滤位于开头的空格
            i += 1
        while i < len(s):  
            if not s[::-1][i].isspace():    #非空格就继续往后走
                count += 1
            else:break                      #遇到空格,直接break
            i += 1
        return count

成绩:82.87 71.09
总结:
1、不要太过依赖与现有的函数,毕竟现在是在刷题,重要的是思想而不是这道题做的有多好(当然把题做好也非常重要)
2、阿哲突然忘了,想起来再说吧

day11

加一

class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        i = 0
        Sum = 0
        for i in range(len(digits)):
            Sum = Sum + digits[::-1][i] * 10 ** i
        Sum = Sum + 1
        result = [0] * len(str(Sum))
        for i in range(len(str(Sum))):
            result[i] = Sum % 10
            Sum = Sum // 10
        return result[::-1]

成绩:83.3 52.28

day12

二进制求和

class Solution:
    def addBinary(self, a: str, b: str) -> str:
        lena = len(a)
        lenb = len(b)
        aEx = a[::-1]
        bEx = b[::-1]
        if lena < lenb:
            for i in range(lena, lenb):
                aEx = aEx + '0'
        elif lenb < lena:
            for i in range(lenb, lena):
                bEx = bEx + '0'
        c = [0] * max(lena, lenb)
        sum = 0
        for i in range(max(lena, lenb)):
            sum = sum + eval(aEx[i]) + eval(bEx[i])
            c[i] = sum % 2
            if sum // 2 != 0:
                sum = 1
            else: sum = 0
        c = ''.join(str(i) for i in c)
        if sum == 1:
            c = c + '1'
        c = c[::-1]
        return c

好几天没刷题了淦,恶魔五月份,今天有空就做一题吧
成绩:5.26 6.95
思路:我一开始想是用位运算,但是既然来做题了,肯定不能这么转来转去。所以我想到了66题:加一。其实两题一样,都是要去模拟正常的运算,但是这个稍微难一点。

day13

x的平方根

代码
至于为啥回到c++了,因为c++yyds,其实写个算法都差不多的,问题不大。

class Solution {
public:
    int mySqrt(int x) {
        //牛顿迭代
        if(x == 0){
            return 0;
        }
        double x0 = x;
        double c = x;
        while(true){
            //牛顿迭代法迭代
            double xi = 0.5*(c/x0+x0);
            if(x0-xi<1e-7){
                break;
            }
            x0 = xi;
        }
        return x0;

    }
};

成绩:57.91 95.97
这道题最标准的方式就是使用牛顿迭代法,通过不断逼近零点的方式得到最接近真实值的估计值。
一开始我在了解牛顿迭代法之后,很迷惑到底在什么时候停止迭代,结果就是当迭代前和迭代后得到的估计值插值足够小时(现在的程序是小于10的-7次方),迭代就结束,当前值和实际值就已经很接近了。
还有一个就是,初始值的选择。一般初始值都选择X,即当前需要求的数。这昂比较合理,不能小于根号X,这样的话会迭代到负根号C。

day14

爬楼梯

class Solution {
public:
    int climbStairs(int n) {
        if(n == 1){
            return 1;
        }
        int *dp = new int[n+1];
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        for(int i =3;i<=n;i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

成绩:100 33.83
思路:这题参数换成档期那楼梯数我估摸着可以用dfs做,刚写过跳马。
空间复杂度可以优化,因为当前方案数是前一阶台阶方案和前两阶台阶方案之和,所以只需要前两个方案数即可,不需要dp数组,这样就能优化成O(1)。

day15

删除排序链表中的重复元素

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(!head){
            return head;
        }
        ListNode* cur = head;
        while(cur->next){
            if(cur->val == cur->next->val){
                cur->next = cur->next->next;
            }
            else{
                cur = cur->next;
            }
        }
        return head;
    }
};

成绩:31.58 71.60
这种题就也没啥好说的,遍历一遍就可以了,主要是头节点有两种,这里就不细说了,反正就蛮简单的这题。

合并两个有序数组

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int i = m - 1;
	    int j = n - 1;
	    int cur = m + n;
	    while (i >= 0 && j >= 0) {
		    if (nums1[i] >= nums2[j]) {
			    nums1[--cur] = nums1[i--];
		    }
		    else if (nums1[i] < nums2[j]) {
			    nums1[--cur] = nums2[j--];
		    }
	    }
	    if (j >= 0) {
		    for (int x = j; x >= 0; x--) {
			    nums1[x] = nums2[x];
		    }
	    }
    }
};

成绩:63.48 65.60
思路:因为要把nums2放到nums1里去,从前向后遍历会导致nums1元素被覆盖,遍历起来很麻烦。所以采用从后向前遍历。双指针控制对应数字的比较,cur指针控制nums1需要插入的位置,对应数字比较后取大插入cur所指位置。当nums1遍历完而nums2仍有元素未遍历时,将nums2剩余元素直接覆盖nums1对应位置即可。nums1未遍历完的情况就不用考虑了。
因为这题的两个数组是顺序数组所以可以这么做,如果不是顺序数组这种做法就难受了。直接插入再排序我觉得就很香。

day16

二叉树中的中序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void print(TreeNode* root, vector<int> &res){
        if(!root){
            return;
        }
        print(root->left, res);
        res.push_back(root->val);
        print(root->right,res);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        print(root, res);
        return res;
    } 
};

成绩:42.06 81.14
思路:这没啥好说的,无非就是这道题输出要格式。

day 17

相同的树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(!p&&!q){
            return true; //两棵树均为空则必相同
        }
        else if(!p||!q){
            return false; //有一棵树为空
        }
        else if(p->val!=q->val){
            return false;  //根节点不相等
        }
        else{
            //深度优先遍历
            return isSameTree(p->left, q->left)&&isSameTree(p->right, q->right);
        }

    }
};

成绩:100 22.04
思路:
深度优先搜索。就其实真的我做了这么些题,还是感觉很懵,就感觉,害,没有吸收什么东西。数据结构这个东西还是得不停地看。递归这个东西说句老实话真不是做个一两道题能明白的,像DFS这种还好,思路比较清晰,像汉诺塔那种,真心理解不了。
那这道题的思路就是这样:
1、首先判断两树当前节点(也就是当前树)是否为空,如果均为空,则说明两树相同,直接返回true;
2、若只有其中一棵树为空,则说明结构不同,直接返回false;
3、若两树都非空,则先比较根节点值是否相同,若不同,则说明两树不同,则返回false;
4、若根节点相同,则深度搜索左子节点和右子节点,若均相同,则返回true,反之则返回false;

day 18

对称二叉树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool check(TreeNode* p, TreeNode* q){
        if(!p && !q) return true;
        if(!p || !q) return false;
        return (p->val == q->val && check(p->left, q->right) && check(p->right, q->left));
    }
    bool isSymmetric(TreeNode* root) {
        if(!root) return true;
        return check(root->left, root->right);
    }
    
};

成绩:85.47 83.14
思路:对于二叉树的算法题,模板很机械。这道题判断一棵二叉树是不是对称树,那就是需要达成两个条件,其一是左右对应点位结构相同;其二是左右对应点位数值相同。当前节点比较完成为true后,继续比较其左右子树。就这样。

二叉树的最大深度

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        return max(maxDepth(root->left), maxDepth(root->right))+1;
    }
};

成绩:100 88.34
提一嘴:大一数据结构期末考试题,当时不会,害。
思路:DFS(属于是套路了)。整棵树的最大深度就是左右树深度较大者加一,那么子树的最大深度也是这么算。于是就这样了。

day 19

将有序数组转换成二叉搜索树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return tree(nums, 0, nums.size()-1);
    }
    TreeNode* tree(vector<int>& nums, int left, int right){
        if(left>right) return nullptr;
        int mid = (left+right)/2; //取中(偶数时为左)作为根节点
        TreeNode* node = new TreeNode(nums[mid]);
        node->left = tree(nums, left, mid-1);
        node->right = tree(nums, mid+1, right);
        return node;
    }
};

成绩:88.51 69.79
思路:
题目给的是有序数组,那么对于一颗二叉搜索树来说,其中序遍历即为有序数组。所以我们利用这一条件,来将该有序数组构造成二叉搜索树。
构造规则:取数组中间值(奇数个为中间,偶数个为中间左边)作为根节点,左边部分做左子树,右边部分做右子树;对于左边部分和右边部分继续进行上述操作。直到数组左指针大于右指针(说明一个元素都没有了),就返回null。
力扣官方解法有三种,其实都一样,无非就是在根节点的选取上有所不同。

day20

平衡二叉树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root){
        if(!root) return 0;
        return max(maxDepth(root->left), maxDepth(root->right))+1;
    }
    bool isBalanced(TreeNode* root) {
        if(!root) return true;
        return (abs(maxDepth(root->left)-maxDepth(root->right))<=1 && isBalanced(root->left) && isBalanced(root->right));
    }
};

成绩:79.58 42.71
思路:
一开始忘了平衡二叉树要求每一个节点都平衡了,主函数就没递归,害。
平衡二叉树的定义是平衡二叉搜索树的任何结点的左子树和右子树高度最多相差1。那么就对每个节点进行递归,判断其左右子树高度差是否不大于1即可。左右子树高度直接用 104、二叉树最大深度代码即可。主函数进行递归。

二叉树的最小深度

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int minDepth(TreeNode* root) {
        if(!root) return 0;
        if(!root->left) return minDepth(root->right)+1;
        else if(!root->right) return minDepth(root->left)+1;
        else return min(minDepth(root->right), minDepth(root->left))+1;
    }
};

成绩:73.85 74.77
思路:
一开始我只是把最大深度的代码改了一下

    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        return max->min(maxDepth(root->left), maxDepth(root->right))+1;

但是提交失败,于是我去看了看错误实例以及题目描述,发现最小深度指的是根节点到最近叶子节点的距离。也就是当碰到像示例二:root = [2,null,3,null,4,null,5,null,6]这种二叉树时期望输出应该是5,但是上述代码输出是1.这个问题就在于对叶子节点的判断。
那么就产生出了三种情况:
1、当当前节点无左子树时,就只对右子树进行递归(该判断条件将无左有右、无左无右两种情况都纳入考虑范围了)
2、当当前节点无右子树时,就只对左子树进行递归;
3、当当前节点同时拥有左右子树时,就对左右子树同时递归。

day21

路径总和

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(!root) return false;
        if(!root->left && !root->right) return targetSum == root->val;
        else return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum-root->val);

    }
};

成绩:68.07 88.95
思路:
今天写这道树的题有点感觉了。
一贯的思维DFS, 这题递归的核心是当前节点到叶子节点的和当前节点子节点到叶子节点的和减去当前节点值是相等的,按照这一思路,形成递归。返回条件是比较叶子节点数值和参数数值是否相同。

路径总和Ⅱ

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:

    vector<int> path; //存储单条路径
    vector<vector<int>> store; //存储所有路径

    void dfs(TreeNode* root, int targetSum){
        if(!root) return;
        path.emplace_back(root->val);
        targetSum -= root->val;
        if(!root->left && !root->right && targetSum == 0){
            store.emplace_back(path);
        }
        dfs(root->left, targetSum);
        dfs(root->right, targetSum);
        path.pop_back();
    }

    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        dfs(root, targetSum);
        return store;
    }
};

成绩:97.29 37.35
思路:这题是112题路径总和的进阶题,112题只需要找到一条符合条件的路即可停止递归。而113题路径总和Ⅱ则需要遍历整棵树,找出所有符合条件的路,才能停止递归。
核心思想和112题是一样的,多出来的部分是路径的存储问题,当找到叶子节点时,判断当前路径是否符合要求,若符合,则将当前路径存入;反之则逐个pop出。路径存储时借鉴的官方的方法,确实很妙,关于树的问题一般都是用DFS,需要递归,确实有时候弄不明白。但有一说一树的递归思路还是相当清晰的,可以通过树去深刻理解一下递归这一思想。

day22

二叉树的前序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void print(TreeNode* root, vector<int> &res){
        if(!root) return;
        res.emplace_back(root->val);
        print(root->left, res);
        print(root->right, res); 
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        print(root, res);
        return res;
    }
};

成绩:100 67.55
思路:和中序遍历没啥区别,返回的顺序不一样。

二叉树的后序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void print(TreeNode* root, vector<int> &res){
        if(!root) return;
        print(root->left, res);
        print(root->right, res);
        res.emplace_back(root->val);
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        print(root, res);
        return res;
    }
};

成绩:100 13.68
思路:同上

day23

杨辉三角

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> res(numRows);
        for(int i = 0;i<numRows;i++){
            res[i].resize(i+1);
            res[i][0] = res[i][i] = 1;
            for(int j = 1;j<i;j++){
                res[i][j]=res[i-1][j-1]+res[i-1][j];
            }
        }
        return res;
    }
};

成绩:100 5.55
思路:这题倒也没什么特别难的思想,就根据杨辉三角的结构特性,运用下标算就行

杨辉三角Ⅱ

class Solution {
public:
    vector<int> getRow(int rowIndex) {
        vector<vector<int>> res(rowIndex+1);
        for(int i = 0;i<=rowIndex;i++){
            res[i].resize(i+1);
            res[i][0] = res[i][i] = 1;
            for(int j = 1;j<i;j++){
                res[i][j]=res[i-1][j-1]+res[i-1][j];
            }
        }
        return res[rowIndex];
    }
};

成绩:100 90.89
思路:要说思路和上一题一模一样的,无非就是取最后一个数组就行。但是我感觉空间复杂度还能优化,因为只要用到相邻两行即可,有空再改改。

class Solution {
public:
    vector<int> getRow(int rowIndex) {
        vector <int> former,later;
        for(int i = 0;i<=rowIndex;i++){
             later.resize(i+1);
             later[0] = later[i] = 1;
             for(int j = 1;j<i;j++){
                 later[j] = former[j-1] + former[j];
             }
             former = later;
        }
        
        return later;
    }
};

离谱了为什么用翻滚数组之后空间反而增多了

day24

验证回文串

leetcode直达

class Solution {
public:
    bool isPalindrome(string s) {
        int left = 0;
	    int right = s.size() - 1;
	    while (left <= right) {
		    while (!isalnum(s[left]) && left<right) {
			    left++;
		    }
		    while (!isalnum(s[right]) && left<right) {
			    right--;
		    }
		    if (s[left] != s[right] && toupper(s[left]) != toupper(s[right])) {
			    return false;
		    }
		    left++;
		    right--;
	    }
	    return true;
    }
};

成绩:86.71 64.39
思路:
采用双指针
left指针从左向右遍历,right指针从右向左遍历,当left大于right时跳出循环
当left所指字符非字母或数字时,往后遍历,直到找到字母或数字;
right同理,不同的地方只有往前遍历;
当left和right均找到字母或数字时,对两者进行比较,若符合要求,继续遍历;反之,跳出并返回false。

只出现一次的数字

leetcode直达

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
	    for (int i : nums) {
		    res = res ^ i;
	    }
	    return res;
    }
};

成绩:94.17 69.79
思路:
这题吧,想到用异或就很简单,想不到异或感觉用哈希表也不难。
能想到异或主要是看了提示:位运算 数组。
反正按照我一开始的思路,遍历数组所有元素,用哈希表存储并统计(数组元素作为键,个数作为值),最后找到值为1的键输出即可。
还有一种就是用位运算的异或:两个位相同为0,相异为1;这意味着什么,意味着异或能够消去两个相同的数字。那和这题不就对上了么。除了需要得到的只出现一次的数字,其余出现两次的数字都会被消去。

day25

环形链表

leetcode直达

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(!head) return false;
        ListNode *fast = head;
        ListNode *slow = head;
        while(fast->next && fast->next->next){
            if(fast->next->next == slow->next){
                return true;
            }
            else{
                fast=fast->next->next;
                slow=slow->next;
            }
        }
        return false;
    }
};

成绩:94 28
思路:
想象一下你们学校开运动会,当碰到3000米这种项目时,是不是就会有套圈的现象出现。究其原因时套圈的跑得快,被套圈的跑得慢。这题就用了这样的思想。
设置快慢两个指针。快指针一次走两个节点,慢指针一次走一个节点。
如果该链表没有环,那么快指针会走到头。
如果有环,那么快指针和慢指针都会进入环(跑道),在跑几次后,慢指针会被快指针追上,此时就能说明该链表有环。
至于为什么要选取走一个节点和走两个节点,因为比较简单。其他选取方案可能会更快,到时候想想。

相交链表

leetcode直达

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    int getLength(ListNode *head){
        int length = 0;
        while(head){
            length++;
            head=head->next;
        }
        return length;
    }
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lengthA = getLength(headA);
        int lengthB = getLength(headB);
        int distract = abs(lengthA-lengthB);
        if(lengthA>lengthB){
            while(distract--){
                headA = headA->next;
            }
        }
        else{
            while(distract--){
                headB = headB->next;
            }
        }
        while(headA && headA!=headB){
            headA = headA->next;
            headB = headB->next;
        }
        return headA;
    }
};

成绩:58 95
思路:
这个题就很难受,也没用到啥技巧,就是经过一遍遍历,统计两条链表的长度,长的那条先走差值个节点,使两条链表的头节点在统一逻辑位置,然后往后遍历就可以。
当两个头节点遍历到同一节点(说明有交叉)或遍历到结尾(说明无节点)时,就返回其中一个头节点即可。
明天看看官方题解。

day26

两数之和 II - 输入有序数组

leetcode直达

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int left = 0;
        int right = numbers.size()-1;
        while(left<right){
            if(numbers[left]+numbers[right]<target){
                left++;
            }
            else if(numbers[left]+numbers[right]>target){
                right--;
            }
            else{
                return {left+1,right+1};
            }
        }
        return {};
    }
};

成绩:
思路:
按照leetcode第一题那么做肯定也可以,但是没有用到非降数组的性质。
一左一右两个指针;
若当前所指两数大于target,右指针左移;
若当前所指两数小于target,左指针右移。
直至找到(题中所示为必定找到)和为target的两数。

day27

回文链表

leetcode直达

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        int count = 0;
        stack<int> stack;
        ListNode* cur = head;
        while(head){ //统计总节点数
            count++; 
            head = head->next;
        }

        int half = count/2;
        
        while(half--){ //前半进栈
            stack.push(cur->val);
            cur = cur->next;
        }
        if(count%2 == 1) cur = cur->next; //奇数位就跳过中间位
        while(cur){
            if(cur->val != stack.top()) return false;
            else{
                stack.pop();
                cur = cur->next;
            }
        }
        return true;

    }
};

成绩:43.48 43.82
思路:
把前一半节点先进栈
若为奇数个节点,则跳过中间节点
对后半节点进行遍历,若当前节点和栈顶元素不相等,则说明非回文链表,返回false;
若遍历完所有节点,说明为回文链表,返回true。
上述方法是用栈做的,但是毕竟最近在集中做双指针的题,所以用双指针再做一遍

移动零

leetcode直达
思路一:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int right = 0;
        int left = 0;
        while(right<nums.size()){
            if(nums[right]){
                nums[left++] = nums[right];
            }
            right++;
        }
        while(left<nums.size()){
            nums[left++] = 0;
        }
    }
};

成绩:43 85.8
思路:
快慢指针:慢指针指向已完成排列字段末尾(即待插入的位置),快指针指向下一个非零元素(即需要往前插入的元素)。
当快指针便利到末尾时,说明往前插值已经完成了。开始移动慢指针,将最后元素均变为零。

思路二:
思路:不同于思路一的往前插值,思路二直接将非零元素和零元素互换(其实就是将快指针指向的元素直接赋0),这样能省去最后换0的过程,能快一些。

day28

反转字符串

leetcode直达

class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0;
        int right = s.size()-1;
        while(left<right){
            swap(s[left++], s[right--]);
        }
    }
};

成绩:87.4 18.4
思路:
这个就很简单,左右指针换就行,这么简单的题放这么后面。

反转字符串中的元音字母

leetcode直达

class Solution {
public:
    bool judge(char s){
        string w = "aeiouAEIOU";
        if(w.find(s) != string::npos) return true; //找到了,说明是元音
        else return false;
    }

    string reverseVowels(string s) {
        int left = 0;
        int right = s.length()-1;
        while(left<right){
            if(judge(s[left]) && judge(s[right])){
                swap(s[left++], s[right--]);
            }
            else if(!judge(s[left])) left++;
            else if(!judge(s[right])) right--;
        }
        return s;
    }
};

成绩:5.8 100
思路:
和上一题夜没差,无非多了个找元音字母的步骤。
这里想讲讲的是判断元音的judge函数中的下述代码

string w = "aeiouAEIOU";
if(w.find(s) != string::npos) return true; //找到了,说明是元音
else return false;

这里面的string::npos指的是string的结束位置。和find一起用的作用就是检查给定字符s是否在给定字符串w里。
类似于python里的in操作。

day29

两个数组的交集

leetcode直达

思路一

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> hash;
        for(int num : nums1){
            hash.insert(num);
        }
        vector<int> res;
        for(int num : nums2){
            if(hash.count(num) > 0){
                if(!count(res.begin(),res.end(),num)){
                    res.push_back(num);
                }
            }
            
        }
        return res;

    }
};

成绩:89.69 80.58
思路:
拿哈希表做就很简单,把其中一个数组存入哈希表,然后遍历另一数组,检查当前元素是否在哈希表中。若在,则说明为相交元素,存入res数组;反之,则说明非相交数组,继续遍历。
有一点要说明,从实例中可以看出,相同相交元素只存一个,所以在存入res数组前还需要检查是否已存在。

思路二:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        sort(nums1.begin(),nums1.end());
        sort(nums2.begin(),nums2.end());
        int key1 = 0; 
        int key2 = 0;
        vector<int> res;
        while(key1<nums1.size()&&key2<nums2.size()){
            if(nums1[key1] == nums2[key2]){
                if(res.size() == 0 || nums1[key1] != res.back()){
                    res.push_back(nums1[key1]);
                }
                key1++;
                key2++;
            }
            else if(nums1[key1] > nums2[key2]){
                key2++;
            }
            else if(nums1[key1] < nums2[key2]){
                key1++;
            }
        }
        return res;
    }
};

成绩:51.24 89.41
思路:
双指针,key1指nums1,key2指nums2;
先对两数组进行排序;
若当前所指两数相同且为存入res,则存入res;
若不相同,所指数较小的指针后移。

两个数组的交集

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        sort(nums1.begin(),nums1.end());
        sort(nums2.begin(),nums2.end());
        int key1 = 0; 
        int key2 = 0;
        vector<int> res;
        while(key1<nums1.size()&&key2<nums2.size()){
            if(nums1[key1] == nums2[key2]){
                res.push_back(nums1[key1]);
                key1++;
                key2++;
            }
            else if(nums1[key1] > nums2[key2]){
                key2++;
            }
            else if(nums1[key1] < nums2[key2]){
                key1++;
            }
        }
        return res;
        }
};

成绩:88.27 95.41
思路:
这题用先排序再双指针遍历的办法会好做很多,因为同时遍历的情况下,能够被对应上的相交元素一定是按照数量较少的数组来对应的,超出的部分是对应不上的。

day30

判断子序列

leetcode直达

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int keys = 0;
        int keyt = 0;
        int slen = s.size();
        int tlen = t.size();
        while(keyt < tlen){
            if(s[keys] == t[keyt]){
                keys++;
            }
            keyt++;
        }
        return keys == slen;
    }
};

成绩:100 90.57
思路:
双指针keys,keyt
对应比较,若两指针所指的字符相同,则一起向后遍历;
反之,则向后遍历主串。
当主串遍历完时,若模式串指针指向模式串末,则说明为子序列;反之,则非子序列。

反转字符串Ⅱ

leetcode直达

class Solution {
public:
    string reverseStr(string s, int k) {
        int n = s.length();
        for(int slow = 0;slow<n;slow+=2*k){
            reverse(s.begin()+slow, s.begin() + min(slow+k, n));
        }
        return s;
    }
};

成绩:9 42.19
思路:
主要是正确定位需要反转字符串的首末位置,其中末位置又因字符长度会有不同(不同情况题里也写了)。其他的就不多说了。

day31

反转字符中的单词Ⅲ

leetcode直达

class Solution {
public:
    string reverseWords(string s) {
        int n = s.length();
        int slow = 0;
        int fast = 0;
        while(fast<n){
            while(s[fast] != ' ' && fast < n) fast++;
            reverse(s.begin()+slow, s.begin()+fast);
            fast++;
            slow = fast;
        }
        return s;
    }
};

成绩:70.80 33
思路:快慢指针,抓住单词之间均用空格隔开的特点即可。
慢指针指单词头,快指针找单词结尾后的空格,两指针之间的即为需要反转的单词。
反转其中一个单词后,快指针+1即为下一指针头,慢指针指向快指针位置。重复即可。

day32

验证回文字符串Ⅱ

leetcode直达

class Solution {
public:

    bool judge(string s, int left, int right){
        while(left<right){
            if(s[left] != s[right]){
                return false;
            }
            left++;
            right--;
        }
        return 1;
    }
    bool validPalindrome(string s) {
        int left = 0;
        int right = s.length()-1;
        while(left < right){
            if(s[left] != s[right]) break;
            left++;
            right--;
        }
        return judge(s, left+1, right) || judge(s, left, right-1);
    }
};

成绩:94.81 8.69
思路:
核心步骤就是跳过第一个不匹配的字符,然后就和最简单的判断回文串步骤一样了;
那么问题就在于怎么跳过第一个不匹配的字符,有两种跳过的情况:
1、多余字符在左半边,则左指针往右跳;
eg.aebcba
2、多余字符在右半边,则右指针往左跳。
跳完这一次之后,若剩余字符串为回文串,则符合题意;反之则说明跳一次不够,不符合题意。

day33

字符的最短距离

leetcode直达

class Solution {
public:
    vector<int> shortestToChar(string s, char c) {
        int len = s.length();
        vector<int> res;
        for(int i = 0;i<len;i++){
            int left = i;
            int right = i;
            while(s[left]!=c && s[right]!=c){
                if(left>0) left--;
                if(right<len) right++;
            }
            res.push_back(max(abs(i-left),abs(i-right)));
        }
        return res;
    }
};

成绩:100 21
思路:
遍历每一个字符,对当前字符设左右双指针,返回较近c和当前字符距离。
有一点需要注意,可能左指针或右指针在先顾总向右遍历时,会遍历到字符头和尾,此时就需要处理距离异常的问题。通过下面这行代码就能确保得到最短且正确(某一指针遍历到边界但另一指针仍在找c这一情况)的距离:

res.push_back(max(abs(i-left),abs(i-right)));

翻转图像

leetcode直达

class Solution {
public:
    void reserve(vector<int>& vec){
        int left = 0;
        int right = vec.size()-1;
        while(left<right) swap(vec[left++],vec[right--]);
    }
    
    
    vector<vector<int>> flipAndInvertImage(vector<vector<int>>& image) {
        int len = image.size();
        for(int i=0;i<len;i++){
            reserve(image[i]);
            for(int j=0;j<len;j++){
                image[i][j] ^= 1;
            }
        }
        return image;
    }
};

成绩:88 40
思路:
这题就没啥可说的,就是模拟,题目有啥要求直接实现就完事儿了。
唯一一点要说的就是“0” 和“1”的替换可以用^(异或)1来完成。

day34

比较含退格的字符串

leetcode直达

class Solution {
public:
    bool backspaceCompare(string s, string t) {
        stack<int> res1;
        stack<int> res2;
        int scur = 0;
        int slen = s.length();
        int tcur = 0;
        int tlen = t.length();
        while(scur<slen){
            if(!res1.empty() && s[scur] == '#'){
                res1.pop();
            }
            else if(s[scur] != '#') res1.push(s[scur]);
            scur++;
        }
        while(tcur<tlen){
            if(!res2.empty() && t[tcur] == '#'){
                res2.pop();
            }
            else if(t[tcur] != '#') res2.push(t[tcur]);
            tcur++;
        }
        return res1 == res2;
    }
};

成绩:100 15
思路:
两个栈,遇到’#‘且栈非空就pop,非’#'就push。
最后比较两个栈即可。

链表的中间节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* slow;
        ListNode* fast;
        slow = fast = head;
        while(fast && fast->next){
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};

成绩:34.46 30.58
思路:
就挺简单的,快慢指针,慢指针一次走一个节点,快指针一次走一个节点。
对于奇数个节点来说,判断条件是一样的,即

while(!fast->next) break;

但如果是偶数个节点,应该有两个中间节点,取第一个和取第二个的判断条件是不一样的;

//取第一个:
while(!fast->next->next) break;
//取第二个
while(!fast->next) break;

按奇偶排列数组

leetcode直达

class Solution {
public:
    vector<int> sortArrayByParity(vector<int>& nums) {
        int slow = 0;
        int fast = 0;
        int numslen = nums.size();
        while(fast<numslen){
            if(nums[fast]%2 == 0){
                swap(nums[slow], nums[fast]);
                slow++;
            }
            fast++;
        }
        return nums;
    }
};

成绩:83.62 49.24
思路:
就是我感觉,我做这类需要按条件前后换的题目,总是下意识的会有这种想法:左指针(慢指针)找到符合条件的那个节点;接着右指针(快指针)找到符合条件的节点;然后两指针所指节点互换。乍一看好像没毛病,但是仔细一琢磨肯定是错了。
这类题,像这题和移动零,其实两题是一个道理,无非前者是换奇偶,后者是换零一。那其实我们不需要去管指针符合什么条件才能换,而是关注应该把谁换上来。比如这题就是把偶数换上来,不需要去管左指针(慢指针)指向的是奇数还是偶数,只需要确保右指针(快指针)当前所指的是偶数即可。这样能达到一种什么效果:左指针左边都是偶数,没有奇数;左指针到右指针之间都是奇数。这就是我们想要的效果。

day35

仅仅反转字母

leetcode直达

class Solution {
public:
    string reverseOnlyLetters(string s) {
        int left = 0;
        int right = s.length()-1;
        while(left<right){
            if(isalpha(s[left]) && isalpha(s[right])){
                swap(s[left++], s[right--]);
            }
            else if(!isalpha(s[left])) left++;
            else if(!isalpha(s[right])) right--;
        }
        return s;
    }
};

成绩:100 72.92
思路:很简单,不说了

按奇偶排序数组

leetcode直达

class Solution {
public:
    vector<int> sortArrayByParityII(vector<int>& nums) {
        int slow = 0;
        int fast = 0;
        int len = nums.size();
        while(fast < len){
            if(slow%2 == 0){
                if(nums[fast]% 2 == 0){
                    swap(nums[slow++], nums[fast]);
                    fast = slow;
                }
            }
            else{
                if(nums[fast]% 2 == 1){
                    swap(nums[slow++], nums[fast]);
                    fast = slow;
                }
            }
            fast++;
        }
        return nums;
    }
};

成绩:9.76 40
思路:
快慢指针,慢指针遍历数组,快指针找对应就数,然后进行替换。其实核心和按奇偶排列数组是一样的,无非是多了两个点(体现在我的代码里):
1、奇偶交替
2、快指针需要被拉回来
但是这样肯定很慢,从成绩里也体现出来了,想想怎么优化。

day36

长按键入

leetcode直达

class Solution {
public:
    bool isLongPressedName(string name, string typed) {
        int slow = 0;
        int fast = 0;
        int namelen = name.length();
        int typedlen = typed.length();
        while(slow<namelen || fast<typedlen){
            if(name[slow] != typed[fast]){
                slow--;
                if(slow == -1 || name[slow] != typed[fast]) return false;
            }
            slow++;
            fast++;
        }
        if(slow == namelen && fast == typedlen) return true;
        return false;
    }
};

成绩:35 76.4
思路:
快慢指针,这是没毛病的
一开始我想的是,用slow指name,用fast之指typed;
若匹配,则fast向右走,直到找到不匹配的位置;
当不匹配时,fast和slow的下一个匹配,直到fast遍历到末尾。
匹配失败的条件就是,当fast找到的下一个不匹配的项和slow的下一个不匹配时,return false。但是当实例3时就会发现不对了,按照这种思路,实例3就是对的。没有考虑到name中又连续相同的两个字符。
说明动fast肯定是不行,那就动slow。两指针均从头开始遍历,若匹配,则均向右走。若不匹配,则slow向左移。为什么要这样,因为长按输出的一定是name里的对应字符,不匹配的的情况要么就是碰到长按输入了;要么就是干脆错了。
为什么第二种可以,因为第二种规避了当name里有连续相同字符,typed有大于连续字符个数的输入时,第一种方法认为typed中的连续字符都是name中对应的第一个字符长按输入造成的

day37

有序数组的平方

leetcode直达

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for(int i = 0;i<nums.size();i++){
            nums[i] = nums[i]*nums[i];
        }
        sort(nums.begin(),nums.end());
        return nums;
    }
};

成绩:87 71
思路:
这题就没啥好说的,平方后排序但是可以借此机会复习一下各种排序。

复写零

leetcode直达
思路1:利用vector的插入和删除操作

class Solution {
public:
    void duplicateZeros(vector<int>& arr){
        int len = arr.size();
        int cur = 0;
        while(cur<len){
            if(!arr[cur]){
            	arr.pop_back();
                arr.insert(arr.begin()+cur+1, 0);
                cur++;
            }
            cur++;
        }
    }
};

成绩:29 64
思路:
cur指针遍历数组,当当前指向的数字为0时,就删除最后一个元素,并在cur位置插入0;
这样当然和这道题的考察点没关系了,但是能做出来。

思路二:双指针

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int len = arr.size();
        int extend = 0;  //需要插入的0的个数
        int cur = 0;     //当前指针
        while(extend+cur<len){  
            if(!arr[cur]){
                extend++;
            }
            cur++;
        }
        //跳出,统计结束,从len-extend开始往后du
        int left = cur-1;
        int right = len-1;
        if(cur+extend == len+1){
            arr[right--] = 0;
        }
        while(left>=0){
            arr[right] = arr[left];
            if(!arr[left]) arr[--right] = arr[left];
            left--;
            right--;
        }
    }
};

成绩:92 30
思路:
两次遍历
第一次遍历计算需要插入的0的个数。我的做法是定义一个遍历指针cur和需要插入的0的个数extend,当cur遍历到0时, extend加1;这时候就会遇到一个问题,什么时候停止遍历。停止遍历的点对第二次遍历来说非常重要。我的做法是当extend+cur>=len时,就跳出循环。那么这个时候会有两种情况:
情况一:插入0后长度刚好等于原数组长度
例如10230450
情况二:插入0后长度比原数组长度大1
例如84500007,那么这种情况就需要特殊处理

第二次遍历复制零。从后往前复制,这样就不会出现从前往后复制出现的覆盖问题。设置双指针left,right。left从右往左遍历数组,起始位置为cur。right指向需要插入的位置,对应第一次遍历出现的两种情况,right的起始位置有两种情况:
情况一:从末尾开始;
情况二:将末尾设为0,从倒数第二位开始。
将left指向的数复制到right位置上,两指针同时向前继续遍历。当left所指数为0时,多进行一次复制,即right多移动一次,达成复写0的效果。

day38

删除回文子序列

对这题表示严厉谴责
leetcode直达

class Solution {
public:
    bool palindriom(string s){
        int left = 0;
        int right = s.length()-1;
        while(left<right){
            if(s[left] != s[right]) return false;
            left++;
            right--;
        }
        return true;

    }
    int removePalindromeSub(string s) {
        if(s.length() == 0) return 0;
        if(palindriom(s)) return 1;
        else return 2;
    }
};

成绩:100 90.09
思路:
别思路了,太扯了,脑筋急转弯家人们。

day39

检查整数及其两倍数是否存在

leetcode直达

class Solution {
public:
    bool checkIfExist(vector<int>& arr) {
        for(int num:arr){
            if(num != 0 && count(arr.begin(), arr.end(), num*2)>0) return true;
            else if(num == 0 && count(arr.begin(), arr.end(), 0)>1) return true;
        }
        return false;
    }
};

成绩:94 91
思路:
遍历一遍就行,找是否有对应两倍数就行;
这种方法有一个需要特殊处理的情况就是0,0的两倍还是0,会找到自己。把0单独拿出来,当遍历到0时,若数组中0的个数大于1,则返回true;

两个数组间的距离值

leetcode直达
思路一:暴力但不完全暴力

class Solution {
public:
    int findTheDistanceValue(vector<int>& arr1, vector<int>& arr2, int d) {
	    int count = arr1.size();
	    for (int num1 : arr1) {
		    for (int num2 : arr2) {
			    if (abs(num1 - num2) <= d) {
				    count--;
				    break;
			    }
		    }
	    }
	    return count;
    }
};

成绩:90 28
思路:
如果两个数组中的所有元素都符合题意,那返回的数就是arr1的size。那当arr1中有几个不符合的,就减几个即可。
那按照这个思路,两个for嵌套,最里面if判断;当出现距离不大于给定d的情况,减1。最后返回return即可。
思路2:优化了个寂寞

class Solution {
public:
    int findTheDistanceValue(vector<int>& arr1, vector<int>& arr2, int d) {
	    int max = *max_element(arr2.begin(), arr2.end());
	    int min = *min_element(arr2.begin(), arr2.end());
	    int count = 0;
	    for (int num : arr1) {
		    if (num > max) {
			    if (abs(num - max) <= d) continue;
		    }
		    else if (num < min) {
			    if (abs(num - min) <= d) continue;
		    }
		    else {
			    sort(arr2.begin(), arr2.end());
			    int p = 0;
			    while (p < arr2.size() - 1) {
				    if (arr2[p] <= num && arr2[p + 1] >= num) {
					    break;
				    }
				    p++;
			    }
			    if (abs(num - arr2[p]) <= d || abs(num - arr2[p + 1]) <= d) continue;
		    }
		    count++;
	    }
	    return count;
    }
};

成绩:不写了,就很低
思路:
加入了比较的环节。
情况一当arr1中当前数比arr2中最大值大时,只需要比较前者与最大值的距离;若两者距离大于d,则arr2中其余数比符合。若两者距离小于d,则当前数不符合要求。
情况二当arr1中当前数比arr2中最小值小时,只需要比较前者与最小值的距离。原因同理;
情况三当arr1中当前数介于arr2最大值和最小值之间时,首先对arr2排序,只要比较arr1当前数与arr2中最靠近当前数的两个数的距离;若均符合距离条件,则其余也符合;反之则不符合,continue。

day40

交替合并字符串

leetcode直达
思路一

class Solution {
public:
    string mergeAlternately(string word1, string word2) {
        string res;
        int len1 = word1.length();
        int len2 = word2.length();
        res.resize(len1+len2);
        int key1 = 0;
        int key2 = 0;
        int key = 0;
        while(key1<len1 && key2<len2){
            res[key++] = word1[key1++];
            res[key++] = word2[key2++];
        }
        while(key1<len1){
            res[key++] = word1[key1++];
        }
        while(key2<len2){
            res[key++] = word2[key2++];
        }
        return res;
        }
};

成绩:100 8.19
思路:
创建一个数组res,将word1和word2两两字符串中的字符依次写入;若其中一字符串遍历完了,就把另一字符串接在后面即可。
这种方法需要额外创建一个长度为word1+word2长度的空间,可以试试在其中一个数组上进行操作。
思路二

class Solution {
public:
    string mergeAlternately(string word1, string word2) {
	    int len1 = word1.length();
	    int len2 = word2.length();
	    word1.resize(len1 + len2);
	    int key1 = len1 - 1;
	    int key2 = len2 - 1;
	    int cur = len1 + len2 - 1;
	    while (key1 != key2) {
		    if (key1 > key2) word1[cur--] = word1[key1--];
		    else word1[cur--] = word2[key2--];
	    }
	    while (key1 >= 0) {
		    word1[cur--] = word2[key2--];
		    word1[cur--] = word1[key1--];
	    }
	    return word1;
        }
};

成绩:100 10.92
思路:
在word1上操作,从后往前依次将word1和word2中的字符交叉写入word1对应位置。
这还只有10.92,到时候去看看解析看看还能怎么优化。

反转单词前缀

leetcode直达

class Solution {
public:

    string reverse(string word, int left, int right){
        while(left<right){
            swap(word[left++], word[right--]);
        }
        return word;
    }
    string reversePrefix(string word, char ch) {
        int cur = 0;
        int len = word.length();
        while(cur<len){
            if(ch == word[cur]) break;
            cur++;
        }
        if(cur == len) return word;
        return  reverse(word, 0, cur);
    }

成绩:100 20.13
思路:
先去找word中第一次出现ch的位置;如果能找到,那就替换包括它之前的字符串;
若找不到,说明name中没有ch字符,就原原本本输出即可。

day41

第一个错误的版本

leetcode直达

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int binary(int left, int right){
        while(left<=right){
            int mid = (right-left)/2+left;
            if(isBadVersion(mid) != isBadVersion(mid - 1)) return mid;
            else if(isBadVersion(mid)) right = mid;
            else if(!isBadVersion(mid)) left = mid+1;
        }
        return 9;
    }

    int firstBadVersion(int n) {
        if(isBadVersion(1)) return 1;
        return binary(1, n);
    }
};

成绩:
思路:
抓住前半部分是false,后半部分是true的特点。
在基本的二分查找的基础上,改变判断条件。
isBadVersion(mid) != isBadVersion(mid - 1),即当前版本为对,前一版本为错,即找到我们需要的版本。
isBadVersion(mid),即当前版本为错,且前一版本也为错,就搜索前半部分的版本;
!isBadVersion(mid),即当前版本为对,且前一版本也为对,就搜索后半部分的版本;

有效的完全平方数

leetcode直达
思路一:调用sqrt

class Solution {
public:
    bool isPerfectSquare(int num) {
        int tmp = sqrt(num);
        if(pow(tmp, 2) == num) return true;
        else return false;
    }
};

成绩:100 68
思路:
这个就没啥好说的,顾码思义。就看num开平方是不是个自然数。
没有用到二分的思想。

思路二:二分找区间

class Solution {
public:
    bool isPerfectSquare(int num) {
        if(num == 1) return true;
        int left = 0;
        int right = num;
        while(right-left>1){
            int mid = (left+right)/2;
            if(pow(mid, 2) == num) return true;
            else if(pow(mid, 2) < num) left = mid;
            else if(pow(mid, 2) > num) right = mid;
        }
        return false;
    }
};

成绩:100 96
思路:
我们都直到,只要一个数不是完全平方数,它的开方就必然在一个长度为1的区间内。比如30的开方就必然在[5,6]内。我们通过二分,就必然能找到对应区间。
具体二分过程看代码即可,没啥难度。

day42

二分查找

leetcode直达

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        while(right>=left){
            int mid = (right-left)/2+left;
            if(nums[mid] == target) return mid;
            else if(nums[mid]<target) left = mid+1;
            else right = mid-1;
        }
        return -1;
    }
};

成绩:96 23
思路:
就是最普通的二分查找,但是边界的选择需要整理一下,有两种选择的方式,明天早八下课回来写。

寻找比目标字母大的最小字母

leetcode直达

class Solution {
public:
    char nextGreatestLetter(vector<char>& letters, char target) {
        int left = 0;
        int right = letters.size()-1;
        if(target>=letters[right] || target<letters[0]) return letters[0];
        while(left<right){
            int mid = (right-left)/2+left;
            if(target>=letters[mid]) left = mid+1;
            else right = mid;
        }
        return letters[right];
    }
};

成绩:58 53
思路:
其他没什么好讲的,讲讲集中需要特殊处理的情况把:
1、当target比letters第一个字符小时,返回第一个字符;
2、当target比letters最后一个字符大或等于最后一个字符时,返回第一个字符;
还有就是这题需要返回的是比目标数字大的最小字符,所以当target==letters[mid]时,仍需往后查找。
例如下面的例子就需要返回n;

[“a”,“a”,“a”, “a”,“a”,“a”,“a”, “n”,“n”]
“a”

day43

山脉数组的峰顶牵引

leetcode直达

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int len = arr.size();
        int left = 0;
        int right = len-1;
        int lmid = 0;
        int rmid = 0;
        while(right-left>1){
            lmid = left+(right-left)/3;
            rmid = left+(right-left)*2/3;
            if(arr[lmid]<=arr[rmid]) left = lmid+1;
            else right = rmid;

        }
        return left;
    }
};

成绩:39.94 30.71
思路:
俗称三分,在[left,right]内取两个点lmid,rmid。
若lmid 反之,则令right = rmid。
有一点就是lmid和rmid的选取可以取三分点,这样的话依次分可以去掉1/3的区间;如果lmid和rmid取中点略微两侧的点,则一次可以去掉1/2的区间,这一种实现方法可以单独设一个mid = (right-left)/2+left, 另一指针设为mid+1即可。
还有一点要注意的使,如果return的是left,那就要让left去逼近峰顶,反之亦然;这体现在lmid=rmid时,究竟是left动还是right动上。

矩阵中战斗力最弱的k行

leetcode直达

class Solution {
public:
    int count(vector<int> &mat){
        int sum = 0;
        while(sum<mat.size() && mat[sum]) sum++;
        return sum;
    }
    vector<int> kWeakestRows(vector<vector<int>>& mat, int k) {
        vector<vector<int>> res;
        for(int i = 0;i<mat.size();i++){
            res.push_back({count(mat[i]),i});
        }
        sort(res.begin(), res.end());
        vector<int> resc;
        int n = 0;
        while(n<k){
            resc.push_back(res[n][1]);
            n++;
            }    
        return resc;
    }
};

成绩:73 12
思路:
瞟一眼代码就能看出这个方法没用二分了。这题里能用二分的点就是去找每一行的最后一个军人(1)第一个平民(0),这样就能统计除每支队伍里的军人的数量了。那我没用二分我咋做的呢,我直接统计了,遍历到0就结束。你说二分行不行,那太行了,你说直接遍历统计香不香,那实在是太香了。
其他的就没什么好说的,搞个二维数组存每一行军人数量序号,再排序输出需要的行数即可。

day44

第k个缺失的正整数

leetcode直达

class Solution {
public:
    int findKthPositive(vector<int>& arr, int k) {
        int n = 1;
        while(k){
            if(!count(arr.begin(),arr.end(),n)) k--;
            n++;
        }
        return n-1;
    }
};

成绩:8.38 76.61
思路:
无二分,看了看官方题解的二分,一下子没看懂,以后有空了再去研究研究。
从1开始遍历,如果遍历到的数不在arr里,说明是缺失的,那么k就减一。直到遍历到k为0,说明当前数就是我们要找的数。

特殊数组的特征值

添加链接描述

class Solution {
public:
    int search(vector<int>& nums, int target) {
	    sort(nums.begin(), nums.end());
	    if (target < nums[0]) return nums.size();
	    if (target > nums.back()) return 0;
	    int left = 0;
	    int right = nums.size() - 1;
	    int mid = 0;
	    while (right >= left) {
		    mid = (right - left) / 2 + left;
		    if (nums[mid] >= target) right = mid - 1;
		    else left = mid + 1;
	    }
	return nums.size()-right-1;
}
    int specialArray(vector<int>& nums) {
        int max = *max_element(nums.begin(),nums.end());
        int left = 0;
        int right =max;
        while(left<=right){
            int mid = (right-left)/2+left;
            if(search(nums,mid)==mid) return mid;
            else if(search(nums,mid)>mid) left = mid+1;
            else right = mid-1;
        }
        return -1;
    }
};

成绩:68.42 63.16
思路:
这题写的有点爽。
辅助函数search找到target所处的区间,返回大于等于target的元素的个数。反映到二分就是查找第一个大于等于target的数的位置。
主函数二分找特征数。二分区间取0~nums.max。利用辅助函数,当search(nums,mid) == mid,即找到了,返回。反之则说明没有符合题意的特征值,返回-1。

day45

公平的糖果棒交换

添加链接描述

class Solution {
public:
    vector<int> fairCandySwap(vector<int>& aliceSizes, vector<int>& bobSizes) {
        int aliceSum = accumulate(aliceSizes.begin(), aliceSizes.end(),0);
        int bobSum = accumulate(bobSizes.begin(), bobSizes.end(),0);
        vector<int> res;
        for(vector<int>::iterator it = bobSizes.begin();it<bobSizes.end();it++){
            int x = (aliceSum-bobSum+2**it)/2;
            if(count(aliceSizes.begin(),aliceSizes.end(),x)){
                res.push_back(x);
                res.push_back(*it);
                break;
            }
        }
        return res;
    }
};

成绩:5.8 89.47
思路:
本方法无二分。
我们设alice给bob的棒糖大小为x,bob给alice的棒糖大小为y。可以得到aliceSum-x+y = bobSum-y+x;化简后即可得到x = (aliceSum-bobSum+2y)/2。可以遍历bobSizes作为y,在aliceSizes中查找通过计算得到的x。当找到符合条件的x时,就return即可。

快乐数

leetcode直达

class Solution {
public:

    int func(int n){
        int sum = 0;
        while(n>0){
            sum+=pow(n%10, 2);
            n = n/10;
        }
        return sum;
    }

    bool isHappy(int n) {
        //vector re s = {1, 10, 13, 31, 23, 32, 100, 68, 86, 82, 28, 19, 91 };
        int count = 50;
        int temp = n;
        while(--count){
            temp = func(temp);
            if(temp == 1) break;
        }
        if(count) return true;
        return false;
    }
};

成绩:好久之前写的,时间成绩找不到了,反正不低就是了。
思路:
这题可以去演演算算几个数,可以发现,每位平方和导致的结果是每个数梯度下降(不要在意)的速度非常快。把int范围内9最多的数拿出来,第一次进行各位平方和就下降到了2000左右。再算几个也是这样。我就是抓住了这一点,直接循环各位平方和50此,在这50次里,如果能到1,那说明是快乐数;反之,则不是。其实根本不用50次,可以循环更小的次数。
还有一点,如果一个树不是快乐数,反复进行各位平方和实惠出现循环的,不知道是不是所有非快乐数都是这样,我去查一查。

day46

下载插件

leetcode直达

class Solution {
public:
    int leastMinutes(int n) {
        if(!n) return 1;
        return ceil(log2(n))+1;
    }
};

成绩:100 34.05
思路:
也算是看了好几遍才看懂这道题的意思
每分钟都有两种选择:
1、不下载,带宽乘2
2、下载,带宽不变
最后选一种使下载最快的方案
语文看不懂,咱把它用数学语言来描述:
比较2n,n~2n,n的斜率。清楚了吧
所以只要求出ceil(log2(n)),此时我们就能求出刚好大于插件数的2n带宽数;最后加一,即进行下载这一步。

day47

按摩师

leetcode直达

class Solution {
public:
    int massage(vector<int>& nums) {
        int len = nums.size();
        if(!len) return 0;
        int dp[len];
        dp[0] = nums[0];
        if(len == 1) return nums[0];
        dp[1] = max(nums[0], nums[1]);
        for(int i = 2;i<len;i++){
            dp[i] = max(dp[i-2]+nums[i], dp[i-1]);
        }
        return dp[len-1];
    }
};

空间优化

class Solution {
public:
    int massage(vector<int>& nums) {
        int len = nums.size();
        if(!len) return 0;
        if(len == 1) return nums[0];
        int former = nums[0];
        int later = max(nums[0], nums[1]);
        for(int i = 2;i<len;i++){
            int temp = later;
            later = max(former+nums[i], later);
            former = temp;
        }
        return later;
    }
};

从O(n)–>O(1)
成绩:100 63
思路:
令dp[i]为到第i个预约为止的最优解。
假设dp[i-1]已知,需要求dp[i]。有两种情况:
1、选择第i个预约,那么第i-1个预约就不能选择,即dp[i-2]+nums[i]
2、不选择第i个预约,即dp[i-1]
那么两者取max即为dp[i]
上述操作中我们可以看出,只有两个需要记录的变量,即dp[i-2dp[i-1],那么就不需要创建数组,直接用former和later滚动记录即可。

连续数列

leetcode直达

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        int max = nums[0];
        for(int i = 1;i<nums.size();i++){
            if(nums[i-1]>0) nums[i] += nums[i-1];
            if(max<nums[i]) max = nums[i];
        }
        return max;
    }
};

成绩:90.36 92.60
思路:
我们要结合dp的核心思想去想这道题,具体思路之前做过那题写了,这次代码就是优化了一下。

三步问题

leetcode直达

class Solution {
public:
    int waysToStep(int n) {
        long dp[1000001];
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 4;
        for(int i = 4;i<=n;i++){
            dp[i] = dp[i-1] + dp[i-2] +dp[i-3];
            dp[i] %= 1000000007;
        }
        return dp[n];
    }
};

成绩:63.05 40.90
思路:
主要就是数据类型的处理,其他没啥

你可能感兴趣的:(学习记录,leetcode,python)