Leetcode刷题数据结构 链表
认识时间复杂度 big O
一个操作的执行时间如果和样本数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
时间复杂度:
- 一个算法流程中常数操作数量的一个指标,常用字母大O表示
- 表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果为 f(N),那么时间复杂度为 O(f(N))
- 评价一个算法流程的好坏的一个可视化指标
- big O 指标 按照算法可能遇到的最差数据情况进行分析,例如插入排序算法
链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
题一:找出两个链表的交点
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 若无交点返回 null
if(headA == null || headB == null){
return null;
}
// 设置两个单链表 临时存放头节点
ListNode l1 = headA;
ListNode l2 = headB;
// 判断两链表头节点是否相交
while(l1 != l2){
//遍历两个单链表 headA headB
l1 = (l1 == null) ? headB : l1.next;
l2 = (l2 == null) ? headA : l2.next;
}
//返回值 若相交,跳出循环,返回相交起始节点值
return l1;
}
}
题二:反转单向链表
递归法:
public ListNode reverseList(ListNode head){
//递归过程终止条件
if(head == null || head.next == null) return head;
//调用递归
ListNode newHead = reverseList(head.next);
//递归过程中 归的过程需要的操作
//建立新的链接
head.next.next = head;
head.next = null;
// 返回值 返回处理好的链表b
return newHead;
}
迭代法:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while(cur != null){
//临时储存cur的下一节点数据
ListNode tmp = cur.next;
//重新定向
cur.next = pre;
//pre 向前移动
pre = cur;
//cur向前移动
cur = tmp;
}
return pre;
}
}
头插法:
class Solution {
public ListNode reverseList(ListNode head){
// 建立一个 作插入的链表
ListNode dummy = new ListNode(-1);
while(head != null){
// 这里总是被迷糊省略掉 刚需的异步==其实就是 链表断开连接前,需将后面的值提前存好,以防丢失
ListNode temp = head.next;
// 将值依次插入到 dummy 链表中,头插法是逆序的
head.next = dummy.next;
dummy.next = head;
//头节点指向剩余未处理的链表部分
head = temp;
}
return dummy.next;
}
}
题三:归并两个有序链表
递归法:
public ListNode mergeTwoLists(ListNode headA, ListNode headB){
if(headA == null) return headB;
if(headB == null) return headA;
if(headA.val < headB.val){
headA.next = mergeTwoLists(headA.next, headB);
return headA;
}else{
headB.next = mergeTwoLists(headA, headB.next);
return headB;
}
}
迭代法:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode preNode = dummy;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
preNode.next = l1;
l1 = l1.next;
}else{
preNode.next = l2;
l2 = l2.next;
}
// preNode 向后瞬移一位
preNode = preNode.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
preNode.next = (l1 == null) ? l2 : l1;
return dummy.next;
}
}
题四:从有序链表中删除重复节点
public ListNode deleteDuplicates(ListNode head){
if(head == null || head.next == null) return head;
head.next = deleteDuplicates(head.next);
return head.val == head.next.val ? head.next: head;
}
题五:删除链表倒数第 n 个节点
public ListNode removeNthFromEnd(ListNode head, int n){
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
//快指针 快走 n 步
for(int i = 0; i < n; i++){
first = first.next;
}
while(first != null){
first = first.next;
second = second.next;
}
// 这里的删除 实质上并未真正删除,只是跳过该结点值
second.next = second.next.next;
return dummy.next;
}
题六:交换链表中相邻的结点
方法一:递归法
class Solution {
public ListNode swapPairs(ListNode head) {
//递归终止条件
if(head == null || head.next == null) return head;
//递归过程需要完成 head,next的交换
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
//返回值 返回处理好的指针 next
return next;
}
}
题七:两数相加
方法一: (栈 + 头插法)
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 建立用来记录l1和l2的各结点值的栈
Deque<Integer> stack1 = new ArrayDeque<Integer>();
Deque<Integer> stack2 = new ArrayDeque<Integer>();
//将结点值压入栈
while(l1 != null){
stack1.push(l1.val);
l1 = l1.next;
}
while(l2 != null){
stack2.push(l2.val);
l2 = l2.next;
}
// 建立一个变量记录进位
int carry = 0;
// 建立 一个头节点 一个动态节点
ListNode ans = null;
// 循环的迭代直到栈为空或者进位为空
while(!stack1.isEmpty() || !stack2.isEmpty() || carry != 0){
int a = stack1.isEmpty() ? 0 : stack1.pop();
int b = stack2.isEmpty() ? 0 : stack2.pop();
int sum = a + b + carry;
carry = sum / 10;
sum = sum % 10;
//这段头插法 很特别!!!
ListNode sumNode = new ListNode(sum);
sumNode.next = ans;
// ans 这个指针始终保持指向sumNode 的这个指针
ans = sumNode;
}
return ans;
}
}
方法二:反转链表 + 头插法
方法三:数组实现
题八:回文链表
递归法:
class Solution {
ListNode temp;
public boolean isPalindrome(ListNode head) {
temp = head;
return check(head);
}
private boolean check(ListNode head){
if(head == null) return true;
boolean result = check(head.next) && (temp.val == head.val);
temp = temp.next;
return result;
}
}
题九:分隔链表
class Solution {
public ListNode[] splitListToParts(ListNode head, int k) {
// 遍历链表,求链表的长度
int n = 0;
ListNode curr = head;
while(curr != null){
n++;
curr = curr.next;
}
// 平均长度
int quotient = n / k;
// 多出来的余数
int remainder = n % k;
// 将链表分割为 k 份
ListNode[] parts = new ListNode[k];
// 每一个链表的头节点, curr 指针用于遍历
curr = head;
// 外面循环将等分不为空的链表装入链数组对应下表位置上
for(int i = 0; i < k && curr != null; i++){
// 初始化的时候把每一个等分链的头节点先存到数组下标对应的位置上,相当于创建好了头节点
parts[i] = curr;
//如果remainder不为0,按照题意要求,优先给前面链分配,在等分长度上加1
int partSize = quotient + (remainder-- > 0 ? 1 : 0);
//把除头节点外剩余节点接到当前节点的后面(原理这么理解),实际上只用让指针遍历到每一个等分的末尾
for(int j = 1; j < partSize; j++){
curr = curr.next;
}
// 重难点:如何跳转到下一个区间
// 断链操作
// 当curr 为尾结点后,拆分与后面结点的链接
ListNode next = curr.next;
curr.next = null;
curr = next;
}
return parts;
}
}
题十:链表元素奇数偶数聚集
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head == null || head.next == null) return head;
// 奇数链头节点
ListNode odd = head;
// 偶数链头节点
ListNode evenHead = head.next;
ListNode even = evenHead;
while(even != null && even.next != null){
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
//将偶数链表部分 放在 奇数链表尾部
odd.next = evenHead;
return head;
}
}
小结
在此记录自己刷题笔记,leetcode分类刷题,先好好学习加复习数据结构基础部分,刚开始就是每天一道,简单的每天2~3道,中等及以上起步就是每天一道,监督学习呀!加油 (●ˇ∀ˇ●)