链表和树的leetcode题

基础新手

链表

注意事项

注意保存上下文环境。注意gc,不要有垃圾变量。换头结点注意考虑头

对于链表不要在乎是O(n)还是O(2n)

长短链表互换

习题

K个节点的组内逆序调整 ?

leetcode:K 个一组翻转链表

找n函数

逆转函数

第一组是否找齐

之后每组处理:start先指向下一组头,遍历完再指向尾

不在于算法,而在于coding能力

链表相加

注意长短链表互换,链表1结点+链表2结点+进位,注意最后进位需要补结点

阶段1:两个相加+进位 while(shortNode != null)

阶段2:单个链表+进位 while(longNode != null)

阶段3:链表结束,仍有进位,补结点 if(carry != 0)

上面三个阶段分别是3个循环

有序链表合并

注意换头,有一个为空就出循环

合并K个升序链表

leetcode:合并K个升序链表

利用小根堆,注意返回空

m条链表,总结点n个。时间复制度 O(log(m)*n)

注意事项

对于全都遍历的,使用递归很方便

习题

判断两棵树是否相同

leetcode:相同的树

注意异或^的使用,只有一个成立时为真

对于全都遍历的,使用递归很方便!!!

判断树是否是镜面树

leetcode:对称二叉树

虽然是简单,但是也做了不少时间

传两个头结点(left,right)!!!我又用了层序遍历

判断left.val == right.val && fun(left.left, right.right) && fun(left.right, right.left)

返回树的最大深度

leetcode:二叉树的最大深度

用递归思想代码十分简单。差点又想用层序

先序遍历和中序遍历建树

leetcode:从前序与中序遍历序列构造二叉树

没啥说的,常见题

二叉树的层序遍历(逆序)

leetcode:二叉树的层序遍历II

注意用linkedlist

没啥,做的工具类都层序了,只不过要逆序而已

层序遍历可以用迭代器(index记录需要运行的次数)避免下标取不对问题

判断是否是平衡二叉树

leetcode:平衡二叉树

本来是easy难度,用递归。

总想着对深度计算的复杂度进行化简,把题做复杂了。先做出来,再优化

返回两个信息(是否平衡/高度),遍历左子树,遍历右子树,判断左右高

平衡搜索二叉树

leetcode:验证二叉搜索树

返回值:树上最大值、最小值、是否是二叉搜索树

能否组成路径和(返回能否)

leetcode:路径总和

递归判断,左边,右边

能否组成路径和(返回所有的路径)

leetcode:路径总和II

和上题一样,只不过把路径存下来

体系学习

排序

选择排序:每次选择最小数,换到最前面

插入排序:后面的依次往前放

冒泡排序:每次出来一个最值,不断交换

二分法:求局部最小。先判断第一个和最后一个,再判断中间相比临近值是上升趋势还是下降趋势

异或

异或:无进位相加

性质:0^N=N N^N=0 满足交换律和结合律

习题

不用额外变量交换两个数

a=a^b

b=a^b

a=a^b

注意,a,b不能指向同一位置

leetcode:交换数字

一个数组一种数出现了奇次,其它数出现偶次,找到这个数。空间O(1),时间O(n)

数组全异或即可

leetcode简单版:只出现一次的数字

整型的int,提取出最右侧的1

a&(~a+1)即a与上自己的取反加一。a的取反加1是自己的负数

一个数组有两个数出现了奇数次,其它数出现偶数次,怎么找到并打印。空间O(1),时间O(n)

1.全^,找到a^b的结果c

2.找到c最右侧的1,这个位置rightOne,a和b其中一个是1,一个是0

3.然后再遍历数组,只异或rightOne是1的,就能找到一个数

4.再拿c^这个数,就得到另一个数了

leetcode简单版:只出现一次的数字iii

一个数组中一种数出现k次,其它数都出现M次,M>1,K<M,找到出现K次的数。空间O(1),时间O(n)

利用一个32位数组,记录数组中每个数二进制位,有1的个数

依次遍历每个数,填充32位的数组(可以不断获取最右侧的1,迅速得到数组该加的位置。再异或最右侧的1,来抵消,while条件)

32位数组里的个数整除M,说明目标数在这一位是0,否则是1(可以利用1右移,与结果取或|)

leetcode简单版:只出现一次的数字ii

常用数据结构

链表翻转、定值删除

master公式

T(N)=a*T(N/b)+O(N^d)的递归函数可以用master公式来确定时间复制度

log(b,a) < d,复杂度为O(N^b)

log(b,a) > d,复杂度为O(N^log(b,a))

log(b,a) == d,复杂度为O(N^d*logN)

哈希表:hashmap/hashset。基本数据类型按值传递,引用数据类型按引用传递

有序表:TreeSet/TreeMap。红黑树、avl树、sb树、跳表。除了增删改查,能获得最小/最大/离某个key最近的。

哈希表增删改查时间复制度都是O(1),有序表功能多时间复杂度都是O(logN)

习题

栈(用数组和链表实现)

数组记最后一个位置即可

链表头插法

队列(用数组和链表实现)

数组需要用循环数组,记录开始位置,结束位置,里面的数据数

尾插法

栈和队列的相互实现

仅用栈实现队列:两个栈

仅用队列实现栈:只能用两个队列,不停取插。复杂度较高。在插入的时候处理,因为peek操作可能多次,保证peek的复杂度低一点

leetcode:用栈实现队列

leetcode:用队列实现栈

实现一个特殊的栈,基本功能+返回当前栈中最小元素,pop,push,getMin时间复杂度O(1)

用两个栈,一个普通的。另一个记录当前栈中最小元素

push的元素比栈中元素小则压入新元素,否则栈中元素再压一个

leetcode: 包含min函数的栈

归并排序

伪代码:

void mergeSort(arr,left,right) {
    int mid = left + ((right - low) >> 1);
    mergeSort(arr, left, mid);
    mergeSort(arr, mid+1, right);
    merge(arr, left, mid, right);
}
void merge(arr, left, mid, right) {
    int help[right - low + 1];
    int i=left,j=mid,index=0;
    while(i<=mid && j<=right) {
        help[index++] = left[i] < right[j] ? left[i++] : right[j++];
    }
    while(i <= mid) help[index++] = left[i++];
    while(j <= right) help[index++] = right[j++];
    arr = help;
}

时间复杂度:T(N)=2*T(N/2) + T(N),根据master log(2,2) = 1,则N * logN

空间复制度:O(N)

习题

一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来叫数组小和。求数组小和。时间复杂度O(nlogn)

利用归并merge过程。

求小和可以转化为某个数右边比它大的有几个,就这个数乘几加到结果上

merge过程分左右数组。

右边比它大的个数 ==> 合并时对于每个左边的数看,右边还没合并的个数

前面数a和后面任意一个数b,如果a>b就是一个逆序对。求所有逆序对的个数。时间复杂度O(nlogn)

找右边比它小的数 ==> 合并时对于每个左边的数看,右边已经合并的个数。只剩左边数组也要计数,相同优先放左边

!!!注意,老师用的从后往前merge。

老师从后往前merge是对的。

如果从前往后,在最后只剩左边数组的时候,也要计数

找右边比它小的数 ==> 从后往前,对于每个左边的数,放到help时,看右边比它小的数有几个。相等优先放右边数组中的数

只剩左边数组是否要考虑,需要考虑只剩左边数组的时候,再放到help时,是否需要计数

leetcode:数组中的逆序对

对于每个数num,求有多少个后面的数乘2依然小于num,求总个数

找右边乘2比它小的数 ==> 合并时对于每个左边的数看,右边乘2小于num的数

leetcode:翻转对

本质上是能快速计算出数量。具体合并可以放在最后不影响时间复杂度,只要能得到数量

给定数组arr,两个整数lower和upper,找arr上多少个子数组(连续的)累加和在[lower,upper]范围上

利用前缀和,先求出前缀和

转化为求 前缀和数组右减左落在[lower,upper]上

即转化为:找右边落在[num+lower,num+upper]上的个数 ==> 合并时对每个左边数看,右边落到[num+lower,num+upper]上的个数,因为num有序,所以两个指针可以不回退

复杂度分析

普通left/right指针,中间加一遍O(n^3)。

用前缀和不用加了,O(n^2)。

用前缀和,之前感觉可以用双指针,单前缀和不是有序的,指针同样需要完全遍历,还是O(n^2)

快速排序

伪代码

void process() {
    mid = partition()   
    // 如果不会,可以不返回区间,就返回一个mid就行。时间复杂度是一样的
    process(left, mid - 1)
    process(mid + 1, right)
}
int partition(arr, left, right) {
    index = left; 
    // 代表小于中间数区域的最右边
    lessEqual = index - 1;
    while(index<right) {
        if (arr[index] <= arr[right]) {
            // 如果一个数小的,那么就和小于区域的最右边,再加1的数互换。即大于区的第一个数
            swap(arr, index, ++lessEqual)
        }
        index++;
    }
    // 把中间值放到中间,即和大于区第一个数交换
    swap(arr, ++lessEqual, R)
    return []
}

感觉老师的代码有误,确认一下

老师的代码非常简洁!!

自己写了很久的代码

    private static int partitionWjx(int[] arr, int left, int right) {
        int midTmp = arr[right];
        int i = left;
        int j = right;
        while (i < j) {
            // 注意至少有一个等于号,否则遇到相等的数字会死循环
            while (i < j && arr[i] <= midTmp) {
                i++;
            }
            if (i < j) {
                swap(arr, i, j);
            }
            while (i < j && arr[j] > midTmp) {
                j--;
            }
            if (i < j) {
                swap(arr, i, j);
            }
        }
        return i;
    }

习题

荷兰国旗,就是数组中有多个0,多个1,多个2,排序。要求空间O(1),时间O(n)

快排的一次partition

leetcode:颜色分类

两个指针,小于区右边界less和大于区左边界more。

如果相等index++,

如果小于则与++less互换,index++

如果大于则与--more互换,index不++,因为这个数刚换过来,还没看过

最后不要忘记把中间的数放过来。

完全二叉树(数组形式)

heapInsert和heapify操作

heapInsert(arr,index):对index位置的数向上调整堆,logN

heapify(arr,index,heapsize):对index位置的数向下调整堆,下标不超过heapsize,logN。将左右节点大的放上来

优先级队列就是堆。大小顶堆

堆排序

建堆O(n*logn),成为大顶堆

最大值和堆尾交换,size--,调整堆。

周而复始,直到全部调整完毕,即size=0,则排序完成

习题

几乎有序的数组,如果把数组排好序,每个节点移动的距离不超过k。选择合适的排序策略

堆排序。找父子节点的下标,很快就能到达k

最大线段重合问题。给定很多线段,每个线段都有两个数[start,end]表示线段开始结束位置,找最多重合区域中包含了几条线段

按start排序。

依次按end的大小进入最小堆中

进去之前,把堆里数字 小于准备进去线段的start 的元素出堆

最多重合区域即堆大小的最大值

加强堆

加强堆是在堆里加一个map,记录值对应的下标在哪里。优点:

1.当想对值更改时,可以迅速找到下标,修改并调整堆

2.当想删除某个堆中元素时,可以迅速找到并调整

给定一个整型数组表示客户编号,和布尔型数组表示购买物品1个和退货1个,求每次购买/退货时购买最多的k个用户。某个用户购买数为0但又退货则忽略,不够k则输出尽可能多的,选k用户按购买商品数量,和时间早优先,替换k时也是。题目在左老师第7节的ppt。代码:class07.Code02_EveryStepShowBoss

两个最小堆:cands存储 候选者(排序方法商品数量多、进入的时间),topK存储top的人(排序方法商品数量少、进入的时间)

有用户购买

是否在cands里,是否在topK里。

在cands里,加

在topk里,加

都没有

topK不满,直接进入topK topK满,进入cands,

有用户退货

是否在cands里,是否在topK里。都没有则忽略

在cands里,减,是否为0

  • 为0,则删除,调整堆

  • 不为0,则减,调整推

在topK里,减,是否为0

  • 为0,则删除,调整堆

  • 不为0,则减,调整堆

比较cands顶是否可以替换topK顶,可以则替换

前缀树

一颗遍历字符串数组的树,节点上有value,pass,end。

value是当前字符,层级代表字符串中第几个出现 pass代表出现的次数 end代表以这个当前路径结尾的次数 遍历每个字符串,沿途节点pass加1,结束时节点end加1

结构

insert(String str)添加某个字符串

search(String str)搜索字符串在有几个。O(str.length)的复杂度

delete(String str)删掉某个字符串

prefixNumber(String str)以str做前缀的有几个。O(str.length)的复杂度

不基于比较的排序

计数排序,数字当下标

基数排序,进到0-9的桶里,不断倒出。可以使用一个桶实现。数字出现的前缀和是下标

排序方法时间复杂度额外空间复杂度稳定性
选择排序O(N^2)O(1)
冒泡排序O(N^2)O(1)
插入排序O(N^2)O(1)
归并排序O(N*logN)O(N)
随机快排O(N*logN)O(logN)
堆排序O(N*logN)O(1)
计数排序O(N)O(M)
基数排序O(N)O(N)

为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并

jdk中排序源码。下面描述的是大致逻辑,但是细节还是不一定一致。总之jdk的排序算法很牛逼,做了非常多的优化,目前我的水平还不足以完全看懂。基本就是插排、快速、归并混合

对于基本数据类型,不需要考虑稳定性

数据量<47,选择插排

数据量>47且<286或不基本有序,用优化的快排。快排会过滤边界有序,且子排序小于47会用插排

数据量>286且基本有序,用归并

对于对象类型

长度小于32用mini-TimSort。

大于31用TimSort

TimSort混用归并、插排、二分搜索等。

 

start

len<286

len<47

是否基本有序,降序组的数量是否大于67

插入排序

快速排序

归并排序

End

链表问题

习题

找链表的中点。遍历不超过n

快慢指针,快走2,慢走1

leetcode:链表的中间结点

判断链表是否回文

找到中点,反转,比较

leetcode:[234]回文链表

链表划分为左边小、中间等、右边大形式。荷兰国旗/颜色划分链表版

三个链表结点依次串起来

带有随机指针的复制

leetcode:[138]复制带随机指针的链表

1.拷贝出的节点放到原节点的next里。

2.复制next指针

3.拆分两个链表

两个有环也可能无环单链表,头结点head1和head2。如果相交返回相交的第一个节点,否则返回null

leetcode:[160]相交链表 [142]环形链表II

首先一个求环的函数,求出两个链表的环

  • 如果一个有环,一个无环则必不相交

  • 如果均无环。终点一样则有相交利用相交链表求,不一样则不相交。

  • 如果均有环。认为第二次到环入口为终点。看终点是否一致,一致则相交,不一致则不相交。一致时,利用相交链表求即可,只不过两个链表的终点一定要选择一样

注意均有环的情况,选定某一个环入口为终点。两个链表终点一定要一样

不给头结点,只给p,能不能删除p节点,或者p前添加一个节点

删除p节点,后一个节点的值移到p,再删除p的下一个节点。地址改不了了,只能改值了

向前添加,新建一个节点,拷贝p的值,然后把p的值改为需要添加的,再把新节点放到p后

leetcode:[237]删除链表中的节点

二叉树

知识

先中后遍历。

无论先中后,左边节点都在左边,右边节点都在右边

先序遍历:父亲在左边,孩子在右边 左边节点都在左边,右边节点都在右边 父亲+左边 自己 孩子加右边

中序遍历:左孩子在左边,有孩子在右边,父亲根据本身在左子树还是右子树可能不同 左边节点都在左边,右边节点都在右边 左边+左孩子+[父] 自己 右边+右孩子+[父]

后序遍历:孩子在左边,父亲在右边 左边节点都在左边,右边节点都在右边 孩子+左边 自己 父亲加右边

所以,先序遍历左边节点和后续遍历右边节点交集是父亲,先右&&后左=孩子,先左&&后左=左边。中序右-先序右=父或空

先中后递归伪代码如下

void traverse(node) {
    // 先序,print(node.val)
    traverse(node.left);
    // 中序,print(node.val)
    traverse(node.right);
    // 后序,print(node.val)
}

层序 leetcode102 二叉树的层序遍历

void traverse(head) {
    Queue queue = new Queue(head);
    while(!queue.isEmpty) {
        node = queue.remove();
        print(node);
        queue.add(node.left);
        queue.add(node.right);
    }
}

先序非递归 leetcode144 二叉树的前序遍历

// 和层序遍历类似,只不过是层序用队列,先序用栈。层序先左后右。先序先右后左。     下面省略了判空操作,注意判空
void traverse(head) {
    Stack stack = new Stack(head);
    while(!stack.isEmpty) {
        node = stack.pop();
        print(node);
        stack.add(node.right);
        stack.add(node.left);
    }
}

后序非递归 leetcode145 二叉树的后序遍历

// 先序是 先左右   调整右左顺序就是 先右左   倒着输出就是 左右先(后序)
void traverse(head) {
    Stack stack = new Stack(head);
    Stack printStack = new Stack();
    while(!stack.isEmpty) {
        node = stack.pop();
        printStack.add(node);
        stack.add(node.left);
        stack.add(node.right);
    }
    while(!printStack.isEmpty){
        node = stack.pop();
        print(node.val);
    }
}

中序非递归 leetcode94 二叉树的中序遍历

// 取当前节点,压栈并向左递归压栈,到底弹出打印转向右边
void traverse(head) {
    Stack stack = new Stack();
    curNode = head
    while(curNode != null || !stack.isEmpty) {
        while(curNode != null) {
            stack.push(curNode);
            curNode = curNode.left;
        }
        curNode = stack.pop();
        print(curNode.val);
        curNode = stack.right;
    }
}

习题

序列化与反序列化

leetcode:297 二叉树的序列化与反序列化

前序、层序、后序。除了中序都可以。后序需要转化为类前序!!!

自己做出来了前序,但是代码调了很多次。老师的代码十分简洁好记。一定记得学习老师的代码!!!不只是学习难题

private String pre(TreeNode root) {
 Queue<String> strQueue = new LinkedList<>();
 pre(root, strQueue);
 return strQueue.toString().replaceAll(" ", "");
}
​
private void pre(TreeNode root, Queue<String> strQueue) {
 if (root == null) {
     strQueue.add(null);
 } else {
     strQueue.add(String.valueOf(root.val));
     pre(root.left, strQueue);
     pre(root.right, strQueue);
 }
}
​
private TreeNode preDe(String data) {
 List<String> collect = Arrays.stream(data.substring(1, data.length() - 1).split(",")).collect(Collectors.toList());
 TreeNode root = preDe(collect);
 return root;
}
​
private TreeNode preDe(List<String> collect) {
 String remove = collect.remove(0);
 if (remove == null || remove.equals("") || remove.equals("null")) {
     return null;
 }
 TreeNode root = new TreeNode(Integer.parseInt(remove));
 root.left = preDe(collect);
 root.right = preDe(collect);
 return root;
}

前序:头左右 后序:左右头 后序.reverse:头右左。不知道为什么,头需要先有

层序就不写了,工具类就是

将N叉树编码成二叉树

leetcode 431 收费题

大孩子左子树,兄弟右子树

看老师的代码,自己写不出来,虽然知道思路

如何设计一个打印整棵树的打印函数

横着打印树,不知道目的是什么

求二叉树最宽的层有多少个节点

leetcode应没有。有个更难的leetcode622,算空节点,需要根据下边减才行

二叉树的后继节点

右的一直左。没右则往父,作为左子树的。找到根没左子树则null

leetcode:剑指off53 二叉搜索树中的中序后继。没有父节点,

我用了中序遍历能过。

答案用二叉搜索树的性质,后继节点是大于p的最小值。p有右则找右的最左;没右则从root开始比较,node=root

p.val < node.val,则后继可能是node或在node的左子树。result更新为node

p.val > node.val,则后继在node的右子树

p.val == node.val,因为之前已经判断过没有右节点了,那就是情况1中result最后更新的结果

折纸问题

对折纸,返回上下折痕。利用树的结构,左子树是down,右子树是up

折纸问题__牛客网

自己做的还是多遍历了一次,不需要先 生成树

是否是完全二叉树

leetcode:958 二叉树的完全性检验

需要子孩子信息:是否满树,是否完全,高度。有以下四种情况

1.满树,高相等

2.左完全,右满,左高-右高=1

3.满树,左高-右高=1

4.左满,右完全,高相等

高是子树最大高+1,满是左满右满且高等,完全有上面四种情况

二叉搜索树

leetcode:98 验证二叉搜索树

左边是,右边是

左边最大<自己<右边最小

这个题需要在前面判空

在里面也能处理,最大值设最小值,最小值设最大值。但是对于普通的int最大值可能过不了

平衡二叉树

leetcode:110 平衡二叉树

左边是,右边是

左高右高差绝对值<=1

是不是满二叉树

方法一:左满+右满+高等

方法二:2^(高度) - 1 = 节点数 高度和节点数也可以通过Info收集

最大的二叉搜索子树的大小、头结点

过当前节点和不过当前节点

过当前节点,左是+右是+左最大<当前<右最小。大小=左节点数+右节点数+1

不过当前节点,大小=左右最大值

class Info{
    bool isSearch;
    int max;
    int min;
    int nodeNum;
    TreeNode maxHead;
}
Info search(TreeNode root) {
    if (root == null) {
        return new Info(true, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, null);
    }
    Info info = new Info();
    Info left = search(root.left);
    Info right = search(root.right);
    if (left.isSearch && right.isSearch && left.max < root.val < right.min) {
        info.isSearch = true;
        info.max = Math.max(root.val, right.max);
        info.min = Math.min(root.val, left.min);
        info.nodeNum = left.nodeNum + right.nodeNum + 1;
        info.maxHead = root;
    } else {
        info.isSearch = false;
        info.max = Math.max(root.val, right.max);
        info.min = Math.min(root.val, left.min);
        info.nodeNum = Math.max(left.nodeNum, right.nodeNum);
        info.maxHead = left.nodeNum > right.nodeNum ? left.maxhead : right.maxhead;
    }
    return info;
}

上面代码正确的:找到二叉树中的最大搜索二叉子树__牛客网

整棵二叉树的最大距离,距离就是此节点到另一个节点所经过的节点数

最大距离不一定经过根结点,但是一定经过某个节点

所以就是某个节点,左深度+有深度 的最大值即可。

牛客:NC195 二叉树的直径 二叉树的直径_牛客题霸_牛客网

a,b最低公共祖先

leetcode:236 二叉树的最近公共祖先

a,b是否都在左,a,b是否都在右。如果都在左则向左,都在右则向右,在不同则当前就是最低公共祖先

info: haveA,haveB,resultNode

left.haveA && left.haveB -> resultNode = left.resultNode , haveA = true, haveB = true

left.haveOne && right.haveOne ... -> resultNode = root , haveA = true, haveB = true

!left.haveOne && !right.haveOne ->

root = One -> resultNode = null, haveA = true, haveB = false

root != One -> resultNode = null, haveA = false, haveB = false

上面是原始思路,比较混乱,下面是整理上面后的思路。

需要返回信息:是否有P,是否有Q

有P:左有+右有+自己是

有Q:左有+右有+自己是

公共祖先:分开则是自己,都在左则拿左的,都在右则拿右的,左/右有1个则判断自己是否是另一个,其它情况就是null

老师的思路更简单一些,公共祖先:左有结果就是做,右有结果就是右,否则有P有Q就是自己

party快乐值

class info {
    int comeHappy; // 来的最大快乐值
    int goHappy; // 不来的最大快乐值
}
main {
    left;right;
    info.comeHappy = left.goHappy + right.goHappy + root.happy;
    info.goHappy = max(left.comeHappy, left.goHappy) + max(right.comeHappy, right.goHappy);
}

上面应该问题不大,不过这个题不是二叉树,是多叉树

class info {
    int comeHappy; // 来的最大快乐值
    int goHappy; // 不来的最大快乐值
}
main {
    left;right;
    info.comeHappy = sum(children, goHappy) + root.happy; // 每个子树goHappy 的和
    info.goHappy = sum(max(each.comeHappy, each.goHappy)); // 每个子树comeHappy,goHappy的最大值 的和
}

贪心算法

对于贪心算法的学习主要以增加阅历和经验为主,无需证明。遇到就会,不是原题就放弃。

习题

一个字符串组成的strs,把所有字符串拼接起来,返回所有拼接结果中,字段序最小的

并不是先排序,再拼接,反例:b,ba 排序结果是b,ba 但是直接拼接的bba大于bab

结论:排序方法是ab < ba则ab在前

牛客:NC85 拼接所有的字符串产生字典序最小的字符串 拼接所有的字符串产生字典序最小的字符串_牛客题霸_牛客网

给点strs,有X表示墙,.表示居民点。居民点可以放灯,能照亮自己及旁边居民。最少的灯照最多的居民

当前可以放(能到这里有个前提,就是前面不用管),下面能不能放,能放放下面否则放这里。当前i

放这里,直接到i+2

放下面,直接到i+3

类似的题,牛客:WY47 安置路灯 安置路灯_牛客题霸_牛客网

注意,牛客的题在障碍物上也能装灯,比上面的题简单一点

金条切两半,划分和长度数值一样的铜板,怎么且省铜板

哈夫曼编码

[10,20,30],转化为不断拿两个数出来加成1个数,再放进去,求怎么拿和最小问题

要捋清楚结果怎么算,堆里,每次出堆累加,到顶时不要加。(这个需要理清楚)

牛客:切金条__牛客网

有会议开始结束时间。在一个会议室里安排最多的会议

按结束时间排序,结束时间短的优先讲。一个会议结束时需要剔除不满足的会议

heap = new PriortyQueue();
each -> heap.add(cat, endTime);
curStartTime
count = 1;
while (heap.size > 0) {
    node = heap.pop();
    if (node.startTime < curStartTime) {
        // 过期任务
    } else {
        // 安排
        count++;
        // curStartTime = node.startTime;  // 这里错了!!!
        curStartTime = node.endTime;
    }
}
return count;

没有大问题,对数器跑过了,有个地方写错了。找leetcode遇到相似的网上贴,其实不一样,这个也告诉我,一定要精心审题,体会用例,不要看到感觉做过就开始,结果可能是驴唇不对马嘴!!!

有净收益数组和花费数组,初始资金和最多参加的项目,求最多收益

按启动资本排序,可以启动的入堆(堆以收益排序)。直到不能启动或全部入堆

拿顶,任务数--,资本加顶收益,再执行上一步

任务书==0 或 没有可启动的 结束

返回当前资本

leetcode 502 IPO

并查集

知识

一种数据结构

并:a,b所在的集合合并。复杂度O(1)

查:a,b是否在同一集合。复杂度O(1)

特点

每个节点有向上指针

a,b结点不断往上找的头相同,就在同一集合

a,b合并,就将代表节点(不断往上找的头结点)合并,b代表节点指向a的代表节点

实现方式

利用集合:所有节点map<id,node>,节点的父亲map<node,node>,代表节点所在集合尺寸map<node,int>。id是原本真实的结构

利用数组:父集合,某个集合里有多少元素,辅助help,总集合数

优化

小集合挂大集合下面

向上找时,沿途的链变扁平。实现方式就是路径上的节点记录下来,找到父节点再遍历一遍直接指向父节点

应用

两块区域合并问题

常用在图等领域

习题

城市相连是一个省,有几个省问题

leetcode 547 省份数量

构造并查集

i,j,par,size i*200+j -> node

new node(i,j,c,1)

totalsize++

遍历数组,union

union(i,j)

复杂度O(2*n*m)

整体没问题,就是找父亲的方式写错了,调了很久

岛问题

leetcode 200 岛屿数量

深度优先,华为od面过

岛问题2

上面的题,变成初始全是0(水),给一个左边变成1。然后输出当前岛屿个数

并查集

集合个数

Node: i,j,par

UnionFind: i,j->node findHead union

main: 来一个节点,加入UnionFind,union上下左右

关键在于加节点的时候,你要知道union什么,肯定不是全union

leetcode 305 岛屿数量II,收费题,老师也没写对数器

岛问题2 - 扩展

如果matrix很大,设计并行计算方案

map:先分段进行,reduce:再对分界点union

知识

邻接表、邻接矩阵

无递归

宽度优先:利用队列

深度优先:利用栈,进节点,出节点并进所有子节点,出节点并进所有子节点

进去之后,从左边出就是自己的兄弟(宽度优先),从右边出就是自己的孩子(深度优先)

拓扑排序

找到所有入度为0

全部删除,继续找

直到结束

最小生成树

就是权值最小的边连接整个图。是最小带权生成树的简称

Kruskal算法(最小生成树算法)

边按权值排序,从小往大进集合

如果没回路就要,否则丢弃

直到遍历完所有边

伪代码

// 判断回路用并查集比较好。
// 判断回路的方式就是看边中还没加进来的点(toNode),和已经加进来的点(fromNode)是否在一个集合里
// 就是只要边中两边的点没有同时已经加过了,就不是环
heap(edges::weight)
while(heap.isNotEmpty) {
    edge = heap.poll();
    if (isSameSet(node1, node2)) {
        result.add(edge);
        union(node1, node2);
    }
}
class Node{
    int val;
    Node parent;
}
class unionFind{
    find: Node ?= Node.parent
    union: node1.parent -> node2.parent
    isSameSet: node1.parent ?= node2.parent
}

Prim算法(最小生成树算法)

任意点出发,加入一个点,解锁与点相邻的边

找到已解锁的最小的边,加入集合,并解锁与边相邻的点

不要环路的边

最短路径

求从v0到各其它顶点的最短路径

迪杰斯特拉

源点加入堆(到v0节点距离升序)

从堆中取点u,处理与u相邻的点。判断经过自己是否更近,更近就更新

min(到v0节点距离+次点到新点,v0本身到新点)

用visited存储是否当过跳转点,当过就不再访问

迪杰斯特拉加强堆优化

由于,过程中会根据实际值找点,更新点。所以用加强堆

能够迅速定位到点,修改点,并调整

习题

网络延迟时间

leetcode 743 网络延迟时间

trans(int[][]) dist[][]
list -> k到其他节点的距离, 初始0,全无穷大
visited -> 是否访问过,初始全为0
拿出list中最小的u,且visited=0
遍历u那一行,判断 dist[u][each]+list[u] ? list[each]  更新list[]
最后list里的最大值就是答案
class Edge{
    Node from;
    Node to;
    int cost; // from到to的距离
}
class node{
    int val;
    int dist; // k到某个节点的最短距离
    bool visited; // 是否被访问过
    List<Edge> edgeList;
}
heap(node::dist);
new allNode(): 1-n   val=n dist=-1   如果是k则是0    
map(n, node);  // 用n找到map的node
times.forEach -> map.get(times[i][0]).getEdgeList().add(edge(map.get(times[i][0]), map.get(times[i][1], times[i][2])));
while(heap.isNotEmpty) {
    node = heap.poll();
    node.visited = true;
    for(edge: node.getEdgeList()) {
        if (!edge.to.visited) {
            if(edge.to.dist > edge.from.dist + edge.cost || edge.to.dist == null) {
                edge.to.dist = edge.from.dist + edge.cost;
                heap.addOrModify(edge.to);
            }
        }
    }
}

暴力递归到动态规划

暴力递归习题

汉诺塔问题

leetcode:汉诺塔

第一遍想的有点问题,没有用长度

本质上是需要长度这个概念的

hanota(A,B,C,n) 将A上的n个盘子移动到C上

字符串全部子序列 无重复元素

leetcode:78 子集

对于一个数字,选或者不选。往下走

subsets(nums, start, result, resultList) {
    // 如果到达末尾则保存,并返回
    start > s.length - 1 ? result.add(s);  return;
    // 不用这个数字
    subsets(nums, start+1, result, resultList);
    // 用这个数字
    result.add(num[start]);
    subsets(nums, start + 1, result, resultList);
    result.add(num[start]);
}

自己以前想过二进制的方式。所有子集个数是2^n,从0-2^n的二进制形式就是所有子集的形式

字符串全部子序列,有重复元素(子集不要重复)

leetcode:90 子集ii

和无重复元素的结构类似

在前面进行一次排序,后面用一个HashSet<String>去重,直接result.toString()放进set里就行

字符串全部排序

leetcode:字符串的排列 剑指offer 38

permutation(s,start,result) {
    // 如果到达末尾则保存,并返回,result用set
    start > s.length - 1 ? result.add(s);  return;
    // 让每个人都有机会当开始值。   当前选某一个数字
    for i start-s.length {
        swap(start,j)
        // 开始值就按这个,后面的值的全排列
        permutation(s,start+1,result)
        swap(start,j)
    }
}

没法说,这种题也不能一次过,还调了许久

字符串全部排列 ,有重复元素(子序列不要重复)

leetcode: 47 全排列ii

和普通全排列一样

最后用hashSet<String>去重即可

1,2,1和1,1,2是不同的子序列,所以不需要排序,只需要去重即可。

1,2,1和1,1,2是相同的子集,所以在之前排序一下

给一个栈,逆序,不申请额外的数据结构,只能使用递归函数

错误思路:pop -> 递归 -> push。 先pop的最后push,并没有逆序

一个移除并返回栈底的函数f

f -> 递归 -> push()。 这才对,把栈底拿到,最后push

函数f: 需依次移除栈顶元素 -> 如果是最后一个就返回 -> 元素再压进去

if (stack.size() == 1) {
    return stack.pop();
} else {
    Integer pop = stack.pop();
    int i = f2(stack);
    stack.push(pop);
    return i;
}

转动态规划习题

机器人从start到end的方法数。在一维上往左往右

对数器做了

往左走方法数 + 往右走方法数

两边拿牌

对数器做了

leetcode:486 预测赢家

两个函数,一个先手拿牌,一个后手拿牌

背包,不超过重量,能装下的最大价值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Rust 是一种现代的编程语言,特别适合处理内存安全和线程安全的代码。在 LeetCode 中,链表是经常出现的目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 Rust 在 LeetCode链表目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安全性。Rust 语言具有如下几个特点: 1. 内存安全性:Rust 语言支持内存安全性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及数据竞争等问,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种数据结构,由一个个节点组成,每个节点保存着数据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据数据元素查找节点和根据指针查找节点等。 Rust 在 LeetCode链表目的练习: 在 LeetCode 中,链表是常见的目类型,而 Rust 语言也是一个非常适合练习链表目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函数来处理链表操作。 例如,针对 LeetCode 中的链表目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表数据结构的知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值