算法题

 

目录

 

一、链表

1.环形链表

2反转链表

3.反转部分链表

4.反转K个一组的链表

5.从后往前K个一组反转链表

6.两数相加I

7.两数相加II

8.归并链表

9.排序链表(O(nlogn)时间复杂度、空间复杂度为O1)

10.删除链表中的重复元素(所有元素)

 

二、二叉树

1.非递归遍历:前、中、后、层

2.二叉树的最小深度

3.二叉树节点个数合集

4.二叉树是否是完全二叉树

5.二叉树的最低公共祖先

6.前序遍历和后序遍历构建二叉树

7.在二叉树中插入节点

8.二叉树内两个节点的最长距离

9.不同的二叉树数量

10.判断二叉树是否是合法的二叉树

 

三、动态规划


一、链表

1.环形链表

方法一:哈希表

方法二:双指针法:

判断是否有环:

public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) {
        return false;
    }
    ListNode slow = head;
    ListNode fast = head.next;
    while (slow != fast) {
        if (fast == null || fast.next == null) {
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    return true;
}

判断入口节点 

参考链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode/

Floyd 算法

想法

当然一个跑得快的人和一个跑得慢的人在一个圆形的赛道上赛跑,会发生什么?在某一个时刻,跑得快的人一定会从后面赶上跑得慢的人。

算法

Floyd 的算法被划分成两个不同的 阶段 。在第一阶段,找出列表中是否有环,如果没有环,可以直接返回 null 并退出。否则,用 相遇节点 来找到环的入口。

阶段 1

1.设定两个初始指针 快指针F 和慢指针S

2.S走到环的入口0时,假设F已经走到距离环入口H的位置

3.那么再走 C-H :

S-》C-H

F-》H+2(C-H)=2C-H=C-H

此时S和F走到一起,证明了F和S指针最终会相遇

阶段2

1.假设F和S现在同时处于距离入口H的位置

2.在相遇前S走的距离是 F+nl+a
注:F是起点到环入口距离 , l是环的长度 , n是走了几圈 , a 是相遇点(离入口距离为a个单位)

3. F在相遇前走的距离是:F+ml+a

4.因为F的速度是S的两倍,所以距离也是S的两倍,因此S的路程*2=F的路程

即:2*(F+nl+a) = F+ml+a

-》F+(2*n-m)l+a = 0  
注:b为入口到交接点的距离,a是交接点到入口的距离,-》a=l-b

-》F+(2*n-m+1)l = b

-》F=b

即表示,从起点走F步和h走F步会同时到达入口处

总结:

1.快慢指针一定会相遇

2.快慢指针相遇后,同时不断向下走一步直至相遇,相遇点就是环的入口点

 编码实现:

1.判断是否有环

2.找到第一次相遇的节点

3.从相遇节点和头结点同时往后走,直到相遇,就是入口节点

public class Solution {
    private ListNode getIntersect(ListNode head) {
        ListNode tortoise = head;
        ListNode hare = head;

        // A fast pointer will either loop around a cycle and meet the slow
        // pointer or reach the `null` at the end of a non-cyclic list.
        while (hare != null && hare.next != null) {
            tortoise = tortoise.next;
            hare = hare.next.next;
            if (tortoise == hare) {
                return tortoise;
            }
        }

        return null;
}

    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }

        // If there is a cycle, the fast/slow pointers will intersect at some
        // node. Otherwise, there is no cycle, so we cannot find an e***ance to
        // a cycle.
        ListNode intersect = getIntersect(head);
        if (intersect == null) {
            return null;
        }

        // To find the e***ance to the cycle, we have two pointers traverse at
        // the same speed -- one from the front of the list, and the other from
        // the point of intersection.
        ListNode ptr1 = head;
        ListNode ptr2 = intersect;
        while (ptr1 != ptr2) {
            ptr1 = ptr1.next;
            ptr2 = ptr2.next;
        }

        return ptr1;
    }
}

拓展:计算出环的长度

 第一步找到第一次相遇点h

然后快慢指针按照原先速度继续前进,再次相遇所走过的数量,就是环的长度

因为他们相遇时,快指针走了两圈,而慢指针走了一圈

public int getLoopLength(ListNode head) {
        ListNode slow = head;
        ListNode fast = head.next;
        while (fast != null || fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast)break;     //第一次相遇
        }
        slow = slow.next;
        fast = fast.next.next;
        int len = 1;
        while (fast!=slow){      //再次相遇
            slow = slow.next;
            fast = fast.next.next;
            len++;
        }
        //当slow再次与fast相遇,得到环的长度
        return len;
    }

2反转链表

 //链表的反转题:要注意next尽量都要放到判断语句中去赋值,避免越界
    public ListNode reverse(ListNode head){
        if (head==null)return null;
        ListNode pre = null;    //存储前一个节点
        ListNode next = null;   //存储后一个节点,断开后可以使用
        while (head!=null){
            next=head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

    //递归
    public ListNode Rreverse(ListNode head){
        if (head==null||head.next==null)return head;
        //递归反转子链表
        ListNode newListNode  = Rreverse(head.next);
        //将下一个元素指向自己,自己指向null,实现反转
        head.next.next = head;
        head.next = null ;
        return newListNode;
    }

3.反转部分链表

1 2 3 4 5  [2,,4]

1 4 3 2 5

package IMUHERO;
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Main {

    public ListNode reverseBetween(ListNode head, int m, int n) {
        ListNode dummyHead=new ListNode(-1);
        dummyHead.next=head;
        ListNode preOne=dummyHead;
        ListNode pre=dummyHead;
        ListNode cur=head;
        ListNode nextNode;
        ListNode reverseEnd;
        int i=0;
        //先把节点移动到范围的前一个位置(preOne表示反转的前一个,cur表示反转的第一个)
        while (i<m-1){
            i++;
            preOne=preOne.next;
            cur=cur.next;
        }
        //让pre成为反转的第一个,cur成为反转的第二个
        pre=cur;
        cur=cur.next;
        reverseEnd=preOne.next;//记录一个还没反转的第一个(之后它就是尾节点)
        while (i<n-1){
            nextNode=cur.next;  //特别注意,这个节点是第三个节点,很容易越界,必须要放在i<n-1判断里面才去实现它
            cur.next=pre;
            pre=cur;
            cur=nextNode;
            i++;
        }
        //最后pre所在位置成为反转范围内的头结点,将首尾相连
        preOne.next=pre;
        reverseEnd.next=cur;
        return dummyHead.next;

    }

}
 

4.反转K个一组的链表

反转K个一组的链表
在做这道题之前,我们不仿先来看看如果从头部开始组起的话,应该怎么做呢?
例如:链表:1->2->3->4->5->6->7->8->null, K = 3。
调整后:3->2->1->6->5->4->7->8->null。
其中 7,8不调整,因为不够一组
public ListNode reverseK(ListNode head , int k){
        ListNode temp = head;   //temp用于移动找到第k个节点
        for (int i=1;i<k&&temp!=null;i++){
            temp=temp.next;
        }
        if (temp==null)return head; //如果不满K,则直接返回head
        ListNode nextListNode = temp.next;  //这是下一部分K节点的起始
        temp.next = null;                   //断开连接,方便反转
        ListNode newListNode = reverse(head);   //按照反转整个链表的思路来反转
        ListNode nextFirst = reverseK(nextListNode,k);  //递归进行
        head.next = nextFirst;              //将前后部分的头尾相接
        return newListNode;                 //返回的是部分反转后的头结点
    }
    //反转一个链表
    private ListNode reverse(ListNode head){
        if (head==null||head.next==null)return head;
        ListNode newListNode = reverse(head.next);
        head.next.next = head;
        head.next = null;
        return newListNode;
    }

5.从后往前K个一组反转链表

给定一个单链表的头节点 head,实现一个调整单链表的函数,使得每K个节点之间为一组进行逆序,并且从链表的尾部开始组起,头部剩余节点数量不够一组的不需要逆序。(不能使用队列或者栈作为辅助)

例如:
链表:1->2->3->4->5->6->7->8->null, K = 3。那么 6->7->8,3->4->5,1->2各位一组。调整后:1->2->5->4->3->8->7->6->null。其中 1,2不调整,因为不够一组。

基本思路和上一题是一样的,只要先整体反转一次链表,然后使用上一题的解法,再反转一次链表即可
-》两次反转,相当于没有反转

public ListNode backReverseK(ListNode head, int k) {
        // 调用逆序函数
        head = reverse(head);
        // 调用每 k 个为一组的逆序函数(从头部开始组起)
        head = reverseK(head, k);
        // 在逆序一次
        head = reverse(head);
        return head;
    }

+第四题的代码 

 

6.两数相加I

给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

进阶:

如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例:

输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7

public ListNode addTwoNumbersI (ListNode l1 ,ListNode l2){
       ListNode dummyHead = new ListNode(-1);
       ListNode cur = dummyHead;
       int carry = 0;
       int sum = 0;
       while (l1!=null||l2!=null){
           int a = l1!=null?l1.val:0;
           int b = l2!=null?l2.val:0;
           sum = a+b+carry;    //注意此处取余
           carry = sum/10;
           cur.next = new ListNode(sum%10);    //注意!要在此处%10
           cur = cur.next;          //注意此处cur = cur.next
           if (l1!=null)l1 = l1.next;   //注意再次判断是否为Null
           if (l2!=null)l2 = l2.next;
       }
       if (carry==1){   //注意最后一次可能为1
           cur.next = new ListNode(1);
       }
       return dummyHead.next;
   }

7.两数相加II

 

给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。

 

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

进阶:

如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例:

输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7

解法一:使用栈

//    1->2->3->4
    //  + 9->8->7->6
    //  ——————————————
    // 1->1->1->1->0
   public ListNode addTwoNumbersII (ListNode l1 ,ListNode l2){
       Stack<ListNode> stack1 = new Stack();
       Stack<ListNode> stack2 = new Stack();
       while (l1!=null){
           stack1.push(l1);
           l1 = l1.next;
       }
       while (l2!=null){
           stack2.push(l2);
           l2 = l2.next;
       }
       int sum = 0;
       int carry = 0;
       ListNode pre = null;
       ListNode cur = null;
       while (!stack1.isEmpty()||!stack2.isEmpty()){
           int a = stack1.isEmpty()?0: stack1.pop().val;
           int b = stack2.isEmpty()?0: stack2.pop().val;
           sum =a+b+carry;
           carry =sum/10;
           cur = new ListNode(sum%10);
           cur.next = pre;
           pre = cur;
       }
       if (carry == 1){
           cur = new ListNode(1);
           cur.next = pre;
       }
       return cur;
   }

解法二:先反转后求和

//    1->2->3->4
    //  + 9->8->7->6
    //  ——————————————
    // 1->1->1->1->0

    public ListNode addTwoNumbersII (ListNode l1 ,ListNode l2){
        ListNode n1 = reverse(l1);
        ListNode n2 = reverse(l2);
        ListNode newListNode = addTwoNumbersI(n1,n2);
        return reverse(newListNode);
    }

    public ListNode reverse(ListNode head){
        if (head==null||head.next==null)return head;
        ListNode newListNode = reverse(head.next);
        head.next.next = head;
        head.next = null;
        return newListNode;
    }

    public ListNode addTwoNumbersI (ListNode l1 ,ListNode l2){
        ListNode dummyHead = new ListNode(-1);
        ListNode cur = dummyHead;
        int carry = 0;
        int sum = 0;
        while (l1!=null||l2!=null){
            int a = l1!=null?l1.val:0;
            int b = l2!=null?l2.val:0;
            sum = a+b+carry;    //注意此处取余
            carry = sum/10;
            cur.next = new ListNode(sum%10);    //注意!要在此处%10
            cur = cur.next;          //注意此处cur = cur.next
            if (l1!=null)l1 = l1.next;   //注意再次判断是否为Null
            if (l2!=null)l2 = l2.next;
        }
        if (carry==1){   //注意最后一次可能为1
            cur.next = new ListNode(1);
        }
        return dummyHead.next;
    }

8.归并链表

1.归并两个链表

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummyHead = new ListNode(-1);
        ListNode cur1=l1;
        ListNode cur2=l2;
        ListNode cur=dummyHead;
        
        while(cur1!=null&&cur2!=null){
            if(cur1.val<=cur2.val){
                cur.next=cur1;
                cur1=cur1.next;
            }
            else{
                cur.next=cur2;
                cur2=cur2.next;
            }
            cur=cur.next;
        }
        cur.next=(cur1!=null)?cur1:cur2;
        return dummyHead.next;
    }
}

递归的归并写法

 private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }

2.归并k个链表

(0)其他方法:

https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode/

(1)方法一:优先队列

时间复杂度:O(Nlogk)

弹出操作时,比较操作的代价会被优化到 O(\log k)O(logk) 。同时,找到最小值节点的时间开销仅仅为 O(1)O(1)。
最后的链表中总共有 NN 个节点。

空间复杂度:O(k)

每次都只将k个链表的最前面丢进去,然后把他们从优先队列取出

用容量为K的最小堆优先队列,把链表的头结点都放进去,然后出队当前优先队列中最小的,挂上链表,,然后让出队的那个节点的下一个入队,再出队当前优先队列中最小的,直到优先队列为空。

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {

        if (lists.length == 0) {
            return null;
        }

        ListNode dummyHead = new ListNode(0);
        ListNode curr = dummyHead;
        PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val - o2.val;
            }
        });

        for (ListNode list : lists) {
            if (list == null) {
                continue;
            }
            pq.add(list);
        }

        while (!pq.isEmpty()) {
            ListNode nextNode = pq.poll();
            curr.next = nextNode;
            curr = curr.next;
            if (nextNode.next != null) {
                pq.add(nextNode.next);
            }
        }
        return dummyHead.next;
    }
}

 (2)方法二:分治

时间复杂度 O(KlogN)

空间复杂度 O (1)

class Solution {
   public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        return merge(lists, 0, lists.length - 1);
    }

    private ListNode merge(ListNode[] lists, int left, int right) {
        if (left == right) return lists[left];
        int mid = left + (right - left) / 2;
        ListNode l1 = merge(lists, left, mid);
        ListNode l2 = merge(lists, mid + 1, right);
        return mergeTwoLists(l1, l2);
    }

    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

9.排序链表(O(nlogn)时间复杂度、空间复杂度为O1)

前面一个函数用来计算中点

下面一个函数用来merge

题解见:https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/

public ListNode sortList(ListNode head){
        if (head==null||head.next==null)return head;
        //使用快慢指针,快指针到达null,或者fast.next==null的时候,slow的位置即是我们的中点位置
        //奇数情况:1->2->3->null        2是中点
        //偶数情况:1->2->3->4->null     2是中点
        ListNode slow = head;
        ListNode fast = head.next;
        while (fast!=null&&fast.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode nextHead = slow.next;
        slow.next = null;
        ListNode left = sortList(head);
        ListNode right = sortList(nextHead);
        return mergeRecution(left,right);//注意!此处是对left和right进行merge,而不是对head和nexthead
        //因为两边分割的链表在向上合并的过程中,头节点可能发生变化
    }


private ListNode mergeRecution(ListNode l1 , ListNode l2){
        if (l1==null)return l2;
        if (l2==null)return l1;
        if (l1.val<l2.val){
            l1.next = mergeRecution(l1.next,l2);
            return l1;
        }
        else {
            l2.next = mergeRecution(l1,l2.next);
            return l2;
        }
    }

10.删除链表中的重复元素(所有元素)

循环版本:

//D->1->null
    //null
    //D->1->1->null
    //D->1->1->2->null
    //D->1->1->2->2->null
    public ListNode delete1(ListNode head){
        if (head==null||head.next==null)return head;
        ListNode dummyHead = new ListNode(-1);
        ListNode pre = dummyHead;
        ListNode cur = head;
        ListNode next = head.next;
        while (next!=null){
            if (cur.val==next.val){
                while (next!=null && cur.val==next.val){
                    next = next.next;
                }
                cur = next;
                pre.next = cur;
                if (next!=null)next=next.next;    //注意防止next越界
            }
            else {
                pre.next = cur;
                pre = cur;
                cur = next;
                next = next.next;
            }
        }
        return dummyHead.next;
    }

递归版本:

public ListNode deleteDuplicates(ListNode head) {
        if (head==null||head.next==null)return head;
        if (head.val == head.next.val){
            while (head.next!=null && head.val == head.next.val){
                head=head.next;
            }
            return deleteDuplicates(head.next);
        }
        head.next = deleteDuplicates(head.next);
        return head;
    }

 

二、二叉树

1.非递归遍历:前、中、后、层

public class Order {
    public List<Integer> preOrder(TreeNode root){
        LinkedList<Integer> list = new LinkedList<>();
        if (root==null)return list;
        Stack<TreeNode>stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode cur = stack.pop();
            list.add(cur.val);
            if (cur.right!=null)stack.push(cur.right);
            if (cur.left!=null)stack.push(cur.left);
        }
        return list;
    }

    public List<Integer> inOrder(TreeNode root){
        LinkedList<Integer>list = new LinkedList<>();
        if (root==null)return list;
        Stack<TreeNode>stack = new Stack<>();
        TreeNode cur = root;
        while (!stack.isEmpty()||cur!=null){
            if (cur!=null){
                stack.push(cur);
                cur=cur.left;
            }
            else {
                cur = stack.pop();
                list.add(cur.val);
                cur = cur.right;

            }
        }
        return list;
    }

    public List<Integer> postOrder(TreeNode root){
        LinkedList<Integer>list = new LinkedList<>();
        if (root==null)return list;
        Stack<TreeNode>stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode cur = stack.pop();
            list.add(0,cur.val);
            if (cur.left!=null)stack.push(cur.left);
            if (cur.right!=null)stack.push(cur.right);
        }
        return list;
    }

    public List<LinkedList<Integer>> levelOrder(TreeNode root){
        LinkedList<LinkedList<Integer>> res = new LinkedList<>();
        if (root==null)return res;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()){
            int size = queue.size();
            LinkedList<Integer>list = new LinkedList<>();
            while (size>0){
                TreeNode cur = queue.poll();
                list.add(cur.val);
                if (cur.left!=null)queue.offer(cur.left);
                if (cur.right!=null)queue.offer(cur.right);
                size--;
            }
            res.add(list);
        }
        return res;
    }
}

2.二叉树的最小深度

public int minDepth(TreeNode root){
        if (root==null)return 0;
        if (root.left==null&&root.right==null)return 1;
        int a = Integer.MAX_VALUE;
        if (root.left!=null){
            a = minDepth(root.left);
        }
        if (root.right!=null){
            a = Math.min(a,minDepth(root.right));
        }
        return a+1;
    }

3.二叉树节点个数合集

1.二叉树中节点的个数:

private int numOfNode(TreeNode root){
        if (root==null)return 0;
        return numOfNode(root.left)+numOfNode(root.right)+1;
    }

2.二叉树叶子节点的个数

private int numOfLeaf(TreeNode root){
        if (root==null)return 0;
        if (root.left==null&&root.right==null)return 1;
        return numOfLeaf(root.left)+numOfLeaf(root.right);
    }

3.二叉树第K层节点的个数

public int numOfKLevel(TreeNode root,int k){
        if(root==null)return 0;
        if (k==1)return 1;
        return numOfKLevel(root.left,k-1)+numOfKLevel(root.right,k-1);
    }

4.二叉树是否是完全二叉树

 

5.二叉树的最低公共祖先

 

6.前序遍历和后序遍历构建二叉树

 

7.在二叉树中插入节点

 

8.二叉树内两个节点的最长距离

 

9.不同的二叉树数量

 

10.判断二叉树是否是合法的二叉树

 

三、动态规划

?mid=&wid=51824&sid=&tid=8555&rid=LOADED&custom1=mp.csdn.net&custom2=%2Fpostedit%2F100673534&t=1568081000984

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IMUHERO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值