Leetcode升级之路(数据结构更新完毕)增加C++版本ing

15 篇文章 3 订阅
3 篇文章 0 订阅

饲养员的教学笔记

复杂度

常见的时间复杂度

  • O(1):
    执行常数次,和输入无关
def O1(num):
	i = num
	j = num*2
	return i+j
  • O(N):
def ON(num):
	total = 0
	for i in range(num):
		total+=i
	return total
  • O(logN):
def OlogN(num);
	 i = 1
	 while(i < num):
	 	i = i*2
	return i
  • O(M+N)
def OMN(num):
	total = 0
	for i in range(num):
		total += 1
	for j in range(num):
		total += j
	return total
  • O(NlogN)
def ONlogN(num1, num2):
	total = 0
	j = 0
	for i in range(num1):
		while(j < num2):
			total += i + j
			j = j*2
	return total
  • O(N^2)
def ON2(num):
	total = 0
	for i in range(num):
		for j in range(num):
			total += i + j
	return total

在这里插入图片描述
O(1) < O(logN) (二分查找) < O(N) < O(NlogN) < O(N^2) < O(2^n) < O(n!)

常见的空间复杂度

算法的存储空间与输入值之间的关系
O(1) < O(N) < O(N^2)
常量看其与输入值得关系
递归要考虑递归栈
O(1)

def ON(num):
	sum = 0;
	for i in range(num):
		sum = sum+i
	return sum

递归
O(1)

def ON(num):
	if(num<=0):
		return 0
	return ON(num-1) + ON(num-2)

数据结构

数组

  • 定义:在连续的内存空间中,储存一组相同类型的元素
  • 数组的访问: 通过索引访问元素。a[0]
  • 数组的内存空间是连续的,增删需要移动后面的元素
  • 二维数组的内存地址是连续的吗?

二维数组实际是一个线性数组存放着其他数组的首地址
在这里插入图片描述

  • 数组的搜索:找到这个元素对应的索引

复杂度:

  • 访问Access:O(1)
    通过计算可以得到地址位置,从而进行访问
  • 搜索search:O(N)
    需要对数组进行遍历
  • 插入insert: O(N)
    需要将后面的元素往后移动
    如果内存不够,需要开辟一块新空间,将数组移进去
  • 删除delete: O(N)
    需要将后面元素往前移

特点

  • 适合读
  • 不适合频繁做增删操作。
  • 场景:读多写少

常用操作

  • 创建数组
a = [];
  • 添加元素
# 末尾添加
a.append(1)
a.append(2)
a.append(3)
# [1,2,3]
# insert(x,y)指定位置x添加y
a.insert(2,99)
# [1,2,99,3]
  • 访问元素
temp = a[2]
# temp = 99
  • 修改元素
a[2] = 88
  • 删除元素
# 删除指定值 O(N)
a.remove(88)
# 1 2 3
# 删除指定索引的元素 O(N)
a.pop(1)
# 1 2
# 删除最后的元素 O(1)
a.pop()
# 1
  • 遍历元素
for i in a:
	print(i)
#
for i in range(0,len(a)):
	print("index:",i,"value:",a[i])
#
for i, val in emumerate(a):
	print("index:",i,"value:",val)
  • 查找元素
index = a.index(2)
  • 数组的长度
len(a)
  • 数组的排序
# 从小到大
a.sort()
# 从大到小
a.sort(reverse =True)

练习题

Leetcode 485

class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        count = 0
        myMax = 0
        for i in nums:
            if(i == 1):
                count =count+1
                myMax = max(myMax,count)
            else:
                count = 0
        return myMax

Leetcode 283

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        j = 0;
        for i in range(0,len(nums)):
            if(nums[i] !=0):
                nums[j] = nums[i]
                j+=1
            else:
                continue
        for k in range(j,len(nums)):
            nums[k] = 0
        

Leetcode 27

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        count = 0;
        j = 0
        for i in range(0,len(nums)):
            if(nums[i] != val):
                nums[j] = nums[i]
                j+=1
            else:
                continue
        return j

链表

  • 非连续空间,包含当前数据和下一节点的地址

复杂度

  • 访问access O(N)
  • 搜索 O(N)
  • 插入 O(1)
  • 删除 O(1)

场景

读少写多

常用操作

  • 创建链表
linkedldist = deque()
  • 添加元素
# 尾部添加
linkedlist.append(1)
linkedlist.append(2)
linkedlist.append(3)
# 指定位置添加 O(n)
linkedlist.insert(2,99)
  • 访问元素
# O(N)
element = linkedlist[2]
# 99
  • 查找元素
    O(N)
index = linkedlist.index(99)
# 2
  • 删除元素
    O(N)
# 删除元素
linkedlist.remove(1)
# 删除指定索引的元素
del linkedlist[2]
  • 更新元素
    O(N)
linkedlist[2] = 88
  • 链表的长度
    O(1)
length = len(linkedlist)

练习题

203

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        dummy = ListNode(0)
        dummy.next = head
        temp = dummy
        while(temp.next):
            if(temp.next.val ==val):
                temp.next = temp.next.next
            else:
                temp = temp.next
        return dummy.next

队列

  • 先进先出
  • 基于链表创建的

单端队列: 一个口进一个口出

双端队列: 两个口都可以进,两个口都可以出

复杂度

  • 访问: O(N)
  • 搜索:O(N)
  • 插入: O(1)
  • 删除: O(1)

常用操作

python

  • 创建队列
# 创建队列
# 该方法为双端队列
queue = deque()
  • 添加元素
# 向末尾添加
queue.append(1)
# 向队列头添加
queue.appendleft(1)
  • 获取即将出队的元素
# 获取即将出队的元素 O(1)
temp1 = queue[0]
# 获取队尾元素 O(1)
temp2 = queue[-1]
temp2 = queue[len(queue)-1]
  • 删除即将出队的元素
# 删除即将出队的元素 O(1)
# 会返回元素
temp2 = queue.popleft()
# 删除最右元素
queue.pop()
  • 判断队列是否为空
# deque每次添加删除元素时会自动计数 O(1)
len(queue) == 0
  • 队列长度
# deque每次添加删除元素时会自动计数 O(1)
len(queue)
  • 遍历队列
# 变删除边遍历
while len(queue) !=0:
	temp = queue.popleft()
	print(temp)

C++

  • 头文件
#include <queue>
  • 创建队列
// 创建队列
// 该方法为双端队列
queue<string> que;
  • 添加元素
# 向末尾添加
que.push("kobe");
  • 删除队列首元素但不返回其值
que.pop();
  • 返回队首元素的值,但不删除该元素
que.front();
  • 返回队列尾元素的值,但不删除该元素
que.back()
  • 判断队列是否为空
que.empty()
  • 队列长度
que.size()

练习题

Leetcode 239
经典单调队列题目

思路:
将滑动窗可以看成一个双端队列。双端队列的最大值放在头部,当头部与滑动窗口出窗的元素相同时,则弹出队头。滑动窗要新进入的元素与队列的尾部元素进行比较,如果比尾部元素大,这弹出尾部元素,因为尾部元素肯定不可能为最大元素。直到尾部元素比要进队元素大或者队列为空时,将进队元素压入队尾。

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        queue = deque()
        res = []
        for i in range(k):
            while (len(queue)!=0 and nums[i] > queue[len(queue)-1]):
                queue.pop()

            queue.append(nums[i])
        res.append(queue[0])
        

        for i in range(k,len(nums)):
            if(queue[0]==nums[i-k]):
                queue.popleft()
            
            while (len(queue)!=0 and nums[i] > queue[len(queue)-1]):
                queue.pop()
            queue.append(nums[i])
            res.append(queue[0])

        return res

Stack

  • 先进后出
  • 基于链表创建的

单端队列: 一个口进一个口出

双端队列: 两个口都可以进,两个口都可以出

复杂度

  • 访问: O(N)
  • 搜索:O(N)
  • 插入: O(1)
  • 删除: O(1)

python

常用操作

  • 创建stack
# 创建stack
stack = []
  • 添加元素
# 向末尾添加
stack.append(1)
  • 获取即将出stack的元素
# 获取即将stack的元素 O(1)
temp1 = stack[-1]
  • 删除即将stack的元素
# 删除即将出stack的元素 O(1)
# 会返回元素
temp = stack.pop()
  • 判断队列是否为空
# stack每次添加删除元素时会自动计数 O(1)
len(stack) == 0
  • stack长度
# stack每次添加删除元素时会自动计数 O(1)
len(stack)
  • 遍历stack
# 变删除边遍历
while len(stack) !=0:
	temp = queue.pop()
	print(temp)

C++

练习题

Leetcode 239

Hash

  • Key - Hash Function - Address
  • Key - Value

哈希碰撞:两个不同的key通过同一个hash函数得到相同的内存地址
在这里插入图片描述
4通过哈希函数解析出的地址也是1,冲突了。
解决方法:

  1. 链表法, 在后面通过链表加入4.bish

复杂度

  • 访问: 没有这个方法
  • 搜索:O(1), 如果有hash碰撞的情况下,就不是O(1)了,为O(K), K为碰撞元素的个数
  • 插入: O(1)
  • 删除: O(1)

常用操作

Python

  • 创建哈希表
# 使用数组创建hash
hashTable = ['']x4
# 使用字典创建hash
mapping = {}
  • 添加元素
# 数组添加
hashTable[1] = 'hanmeimei'
hashTable[2] = 'lihua'
hashTable[3] = '233'
mapping[1] = 'hanmeimei'
mapping[2] = 'lihua'
mapping[3] = '233'
  • 修改元素
hashTable[1] = 'unspoken'
mapping[1] = 'erd'
  • 删除
hashTable[3] = ''
mapping.pop(1)   # 删除key=1的值
# 或者使用del
del mapping[1]
  • 获取Key对应的值
hashTable[3]
mapping[2]
  • Key是否存在
#数组类型的只能遍历
# 返回true或false 
3 in mapping
  • hash长度
len(mapping)
# empty?
len(mapping)==0

C++

map可以分为4类:
map: 按顺序保存,key对应 唯一 value的map
multimap; 按 顺序 保存,key可对应 多个 value的map
unordered_map: 无序 保存,key对应 唯一 value的map
unordered_multmap: 无序 保存,key对应 多个 value的map

  • 头文件
#include <map>
  • 创建哈希表
// 未初始化
map<int, string> hashTable;
// 初始化
map<int, string> hashTable = {
                { 2015, "Jim" },
                { 2016, "Tom" },
                { 2017, "Bob" } };
  • 添加元素
// 使用[ ]进行单个插入
hashTable[2018] = "kobe";
// 使用insert插入
// 插入单个值
hashTable.insert(std::pair<int, std::string>(2019,"lebron"));
// 指定位置插入
std::map<int, std::string>::iterator it = hashTable.begin();
hashTable.insert(it, std::pair<int, std::string>(2011,"ivson")); 
// 列表形式插入
hashTable.insert({{2012,"end"},{2033,"punk"}});
  • 修改元素
hashTable[2018] = 'unspoken'
  • 删除
// 删除迭代器指定位置,并返回一个指向下一元素的迭代器
hashTable.erase(hashTable.begin());
// 删除一定范围内的元素,并返回一个指向下一元素的迭代器
// map的迭代器是双向迭代器,只能it++, it--, ++it, --it操作,不能,it+5这样操作
// http://c.biancheng.net/view/338.html
// 删除[a, b]的所有元素
auto it = hashTable.begin();
it++;
hashTable.erase(hashTable.begin(), it);
// 根据Key来进行删除, 返回删除的元素数量,在map里结果非0即1
size_t num = hashTable.erase(2012);
// 清空map,清空后的size为0
hashTable.clear();
  • 获取Key对应的值
    Map中元素取值主要有at和[ ]两种操作,at会作下标检查,而[]不会。
// key 2000不在hashtable中,使用[]不会报错,会插入该key,但不会显示内容
std::cout<<hashTable[2000] <<std::endl;
// at会报错
std::cout << hashTable.at(2000) <<std::endl;
  • Key是否存在
// 使用find进行查找,找到则返回指向该关键字的迭代器,否则返回指向end的迭代器
if(hashTable.find(2011)!=hashTable.end()){
	std::cout << "find it" <<std::endl;
}

  • hash长度
hashTable.size();
# empty?
hashTable.empty();
  • 排序
// map不支持c++内置的sort,所以使用vector<pair<int, string>> 当做伪map,然后再用sort处理
vector<pair<int, string>> mymap = {{1000,"M"},{900,"CM"},{500,"D"},{400,"CD"},{100,"C"},{90,"XC"},{50,"L"},{40,"XL"},{10,"X"},{9,"IX"},{5,"V"},{4,"IV"},{1,"I"}};
// 将其从小到大排序
sort(mymap.begin(),mymap.end(),[](const pair<int, string>& a, const pair<int,string>& b){
            return a.first <= b.first;
        });

练习题

Leetcode 217

思路:
将数组中的数看做key,出现相同的key,则在key对应的value上+1
然后判断,如果有value>1的情况,则说明有重复元素,返回true

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        mapping = {}
        for i in nums:
            if i not in mapping:
                mapping[i] = 1
            else:
                mapping[i] = mapping[i]+1
            if mapping[i] > 1:
                return True
        return False

Leetcode 389

思路:
将字符串中的元素看做key,出现相同的key,则在key对应的value上+1
然后对t字符串的元素进行遍历,如果该元素没有出现在mapping中,或者该元素对应的value为0,则返回该元素

class Solution:
    def findTheDifference(self, s: str, t: str) -> str:
        mapping = {}
        for i in s:
            if i not in mapping:
                mapping[i] = 1
            else:
                mapping[i]+=1
        
        for i in t:
            if i not in mapping or mapping[i] is 0:
                return i
            else:
                mapping[i]-=1
        
        return "a"

Leetcode 496

思路:
单调栈的应用:
场景:寻找下一更大元素(单调递减栈),或更小元素(单调递增栈);
使栈里的元素呈现单调递增或者递减
以此题为例,如果想要让栈里元素递减,则当要入栈元素>栈顶元素,则弹出栈顶元素,并用哈希表保存栈顶和入栈元素。直至出现比入栈元素大的栈顶元素,或者栈空。
遍历完元素后,栈中剩余元素,规定其对应的值为-1,存入hash表中

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        stack = []
        mapping = {}
        for i in nums2:
            if len(stack) == 0:
                stack.append(i)
            else:
                while len(stack) > 0 and stack[-1] < i:
                    t1 = stack[-1]
                    mapping[t1] = i
                    stack.pop()
                stack.append(i)
        
        while len(stack) != 0:
            mapping[stack[-1]] = -1
            stack.pop()
        
        res = []
        for i in nums1:
            res.append(mapping[i])
        
        return res        

Set

  • 无序
  • 不重合

主要作用:检查某一个元素是否存在
有没有重复元素

复杂度

  • 访问: 没有这个方法
  • 搜索:O(1), 如果有hash碰撞的情况下,就不是O(1)了,为O(K), K为碰撞元素的个数
  • 插入: O(1); 有hash冲突O(k)
  • 删除: O(1); 有hash冲突O(k)

常用操作

  • 创建Set
# 使用数组创建hash
s=set()
  • 添加元素
s.add(18)
s.add(10)
s.add(1)
  • 删除
s.remove(2)
  • 元素是否存在
# 返回true或false 
3 in s
  • hash长度
len(s)
# empty?
len(s)==0

C++

set可以分为4类:
set: 按顺序保存,元素只能出现1次
multiset; 按 顺序 保存,元素只能出现多次
unordered_set: 无序 保存,元素只能出现1次
unordered_multset: 无序 保存,元素只能出现多次

  • 节点:除了根节点和叶子节点外的节点
  • 根节点:第一个开始的节点
  • 叶子节点:最底层的节点,没有孩子、
    在这里插入图片描述
    深度:从上往下计算
    高度:从下往上计算
    层:从上往下计算。1开始
    在这里插入图片描述

遍历:
二叉树d的遍历

  • 前序遍历:先访问根节点,然后访问左节点,最后右节点
  • 中序遍历:先访问左节点,然后访问根节点,最后右节点
  • 后续遍历:先访问左节点,然后访问右节点,最后根节点

前缀树

  • 匹配与前缀相同的字符串
    在这里插入图片描述

复杂度

插入: O(N)
搜索:O(N)
前缀(prefix): O(N)

堆(heap)

  • 完全二叉树
  • 每个节点>=(最大堆) or <=(最小堆)孩子节点

复杂度

访问(acess):无
搜索:O(1) (堆顶)
添加:O(logN)
删除:O(logN) 一般是堆顶
在这里插入图片描述
在这里插入图片描述
Leetcode 208, 720, 692

复常用操作

  • 创建堆
# 使用数组创建heap
minheap = []
heapq.heapify(minheap)
  • 添加元素
heapq.heappush(minheap,10)
heapq.heappush(minheap,8)
heapq.heappush(minheap,6)
heapq.heappush(minheap,2)
heapq.heappush(minheap,11)
  • 查看堆顶元素
# peek
print(minheap[0])
  • 删除
heapq.heappop(minheap)
  • 长度
len(minheap)
# empty?
len(s)==0
  • Iteration
while len(minheap)!=0:
	print(heap.heappop(minheap))

如何获取最大堆
将元素取反,然后对其进行最小堆处理,即是最大堆
如1,3,6,8
取反-1,-3,-6,-8
最小堆的堆顶-8,-(-8) = 8即为最大堆的堆顶

  • 无向图

  • 有向图

  • 权重图

  • 入度:多少边指向该顶点

  • 出度:多少边从这个点指向别的顶点

数据结构知识点回顾

各种数据结构:

  • 访问
  • 搜索
  • 插入
  • 删除
    各种数据结构相对应的时间复杂度

算法部分

二分查找

适用场景

一般适用场景是有序数组中查找指定元素。

模板

大佬的模板

  1. 目标值包含在我们设置的范围中(通过mid来判定)、
# Input: vector<int> nums, targer
int left = 0;
int right = nums.size()-1;
while(left <=right){
	# 为了防止溢出
	int mid = left + (right - left) /2;
	if(nums[mid] == target){
		return mid;
	}else if(nums[mid] < target){
		left = mid+1;
	}else{
		right = mid - 1;
	}
}
  1. 每次迭代都是为了排除非目标数。朝着目标数逐渐逼近。最后的left有可能是target。有时候还需要对跳出循环的left进行判断处理。
# Input: vector<int> nums, targer
int left = 0;
int right = nums.size()-1;
while(left < right){
	int mid = left + (right - left)/2;
	if(nums[mid] < target){
		left = mid+1;
	}else{
		right = mid;
	}
}
if(nums[left] == target){
	return left;
}else{
	return -1;
}

注意事项

在写二分法题目时,一定要注意各自变量代表的含义,特别是区间的开闭情况。

一般我喜欢写区间,前后都为闭区间[left,right]

while的跳出条件

left <right; 这时循环中不能判定left==right的情况,即无法判定最后的mid
left<=rigth;这时循环中可以判定left==rigth的情况。循环中应该判定nums[mid] == target.

切记二分法不能生搬硬套。一定要理解,灵活变通,特别是边界条件的判断。

特殊情况

异或问题

  • 运算方法
int a = 12;
int b = 16;
// ^异或位运算符
cout << a^b << endl;

异或问题的题目常常会使用异或运算的下列性质:
对于特定数组 [a1, a2, a3, … , an][a1,a2,a3,…,an],要求得任意区间 [l, r][l,r] 的异或结果,可以通过 [1, r][1,r] 和 [1, l - 1][1,l−1] 的异或结果得出:
xor(l,r)=xor(1,r)⊕xor(1,l−1)
因为:
xor(1,l-1)⊕xor(1,l-1) = 0
xor(1,r) = xor(1,l-1)⊕xor(l,r)
所以一般可以先遍历数组将异或结果存入数组。

位运算

符号描述运算规则
&两个位都为1时,结果才为1
|两个位都为0时,结果才为0
^异或两个位相同为0,相异为1
~取反0变1,1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

左移,右移时注意是否有符号
无符号类型: 最高位仍然表示数值
有符号类型: 最高位表示符号,1为负,0为正
左移: << ; 无符号数–> 最右补零;有符号数: 最右补零,最高位可能变化,正负号可能改变。
右移: >> ; 右符号数–>左边补0;有符号数: 左边补0或1,保持符号位。
注意优先级,添加括号

char类型的运算对象首先提升成int型,提升时原来位保持不变,高位补零。

  • 获得int型最大值
int getMaxInt(){
        return (1 << 31) - 1;//2147483647, 由于优先级关系,括号不可省略
}
  • 乘以2的m次方
int mulTwo(int n,int m){//计算n*2 
	return n << m;
}
  • 除以2的m次方
int divTwo(int n,int m){//负奇数的运算不可用
	return n >> m;
}
  • 判断一个数的奇偶性
boolean isOddNumber(int n){
	return (n & 1) == 1;
}
  • 从低位到高位,取n的第m位
int getBit(int n, int m){
	return (n >> (m-1)) & 1;
}
  • 从低位到高位.将n的第m位置1
int setBitToOne(int n, int m){
	return n | (1 << (m-1));
	/*将1左移m-1位找到第m位,得到000...1...000
	  n在和这个数做或运算*/
}
  • 从低位到高位,将n的第m位置0
int setBitToZero(int n, int m){
	return n & ~(1 << (m-1));
	/* 将1左移m-1位找到第m位,取反后变成111...0...1111
	   n再和这个数做与运算*/
}

例题

1310. 子数组异或查询
1442. 形成两个异或相等数组的三元组数目

  • 28
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值