LeetCode刷题 数组、链表

数组

1、两数之和

描述:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

解题思路:用一个map来存储目标数据值,避免暴力解析获得结果。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] indexs = new int[2];
        HashMap<Integer,Integer> hash = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(hash.containsKey(nums[i])){
                indexs[0] = i;
                indexs[1] = hash.get(nums[i]);
                return indexs;
            }
            hash.put(target-nums[i],i);
        }
        return indexs;
    }
}

15、三数之和

描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
思路:这道题与两数之和有一定区别,两数之和可以直接用list放置,但是三数和这么做时间复杂度过高O(n^2). 因此先将数组排序,得到的有序数组中,设置一个目标target=0-a,然后再对剩下的两个数字进行筛选。 递增数组可以选择二分查找,两根指针的方法,此处使用双指针的方法。
注意点: 由于不包含重复元素,因此要去重。只要有两个元素重复,则跳过

class Solution {
    List<List<Integer>> res=new ArrayList();
    public List<List<Integer>> threeSum(int[] nums) {
        int len=nums.length;
        Arrays.sort(nums);// 从小到大排序
        for(int i=0;i<len;i++){
            if(nums[i]>0) break; //简化,如果>0则说明该三数之和不可能为0
            if(i>0&&nums[i]==nums[i-1]) continue; //去重
             int target=0-nums[i];
            int l=i+1,r=len-1; //此处必须对i后面的数字进行筛选,不能重复
            while(l<r){
                List<Integer> list=new ArrayList();
                if(nums[l]+nums[r]==target){
                    list.add(nums[i]); list.add(nums[l]); list.add(nums[r]);
                   res.add(list);
                while(r>l&&nums[l+1]==nums[l])l++; //这个地方改成l-1只会出现一个结果了
                while(r>l&&nums[r]==nums[r-1])r--;
                      l++;r--;
                }
                else if(nums[l]+nums[r]>target)r--;
                else l++;
            }
        }
        return res;
    }
}

7、整数反转

描述:给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
思路:求余数跟缩小1/10的灵活运用,用Long类型来判断是否越界。

class Solution {
    public int reverse(int x) {
            long n=0;
            while(x!=0){
                n = n*10 + x%10; 
                x = x/10;
            }
            // 最终判断数据是否越界
            return (int)n ==n ?(int)n:0;
    }
}

11、盛水最多的容器

描述:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
在这里插入图片描述
思路:开始跟结束两个指针,不断向中间移动看是否可以让面积增大。

class Solution {
    public int maxArea(int[] height) {
        int i = 0;
        int j = height.length-1;
        int res = 0;
        while(i<j){
            res = height[i] < height[j] ? Math.max(res,(j-i)*height[i++]):Math.max(res,(j-i)*height[j--]);
        }
        return res;
    }
}

42、接雨水(经典题)

在这里插入图片描述
暴力

class Solution():
    def trap(self,height):
        water = 0
        for i in range(len(height)):
            max_left , max_right = 0
            for j in range(0,i): #遍历数值每个位置
                max_left = max(max_left,height) #查找位置i左边的最大值
            for j in range(i,range(len(height))):
                max_right = max(max_right,height) #查找位置i右边的最大值
            if min(max_left,max_right)>height[i]:
                water += min(max_left,max_right) - height[i] #最大值的最短板决定储水量
        return water

双指针版本:如果left_max<right_max成立,那么它就知道自己能存多少水了。无论右边将来会不会出现更大的right_max,都不影响这个结果

class Solution:
    def trap(self, height: List[int]) -> int:
        left=0
        right=len(height)-1
        left_max=right_max=0
        ans=0
        while left<=right:
            if left_max<right_max:
                ans += max(0,left_max-height[left])
                left_max = max(left_max,height[left])
                left += 1
            else:
                ans += max(0,right_max-height[right])
                right_max = max(right_max,height[right])
                right -= 1
        return ans

双指针取巧版:这里注意 柱子加了两次,能灌水的面积加了两次,还有一个大矩形。

class Solution {
    public int trap(int[] height) {
         int ans = 0;
         int h1 = 0;
         int h2 = 0;
         for(int i =0;i<height.length;i++){
             h1 = Math.max(h1,height[i]);
             h2 = Math.max(h2,height[height.length - i -1]);
             ans += h1 + h2 - height[i];// 实体柱子面积加了两次,左到右跟右到左重合部分是真正接水面积。
         }
         return ans - height.length * h1;//减矩形面积
    }
}

Java单向栈做法

public int trap(int[] height) {
    int ans = 0, current = 0;
    Deque<Integer> stack = new LinkedList<Integer>();
    while (current < height.length) {
        while (!stack.isEmpty() && height[current] > height[stack.peek()]) {
            int top = stack.pop();
            if (stack.isEmpty())
                break;
            int distance = current - stack.peek() - 1;
            int bounded_height = Math.min(height[current], height[stack.peek()]) - height[top];
            ans += distance * bounded_height;
        }
        stack.push(current++);
    }
    return ans;
}

26、删除排序数组中的重复项

描述:给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

在这里插入图片描述

class Solution {
    public int removeDuplicates(int[] nums) {
        if( nums.length == 0 ) return 0;
        int i = 0;
        for(int j=1;j<nums.length;j++){
            if(nums[i] != nums[j]){
                i++;
                nums[i] = nums[j];
            }
        }
        return i + 1;
    }
}

283、移动零 跟26一样。

描述:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
思路:简单

class Solution():
    def moveZeroes(self, nums: List[int]) -> None:
	    for num in nums:
	    	if num == 0:
	    		nums.remove(0)
	    		nums.append(0)
	    return nums      

java:

class Solution {
    public void moveZeroes(int[] nums) {
        if(nums == null || nums.length <=1) return;
        int index = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i] !=0){
                nums[index++] = nums[i];
            }
        }
        while(index <nums.length) nums[index++] = 0;
    }
}

Python:经典的双指针解法,慢指针记录非零元素的尾标,快指针遍历整个数组

class Solution():
    def moveZeroes(self, nums: List[int]) -> None:
	    slow,fast = 0,0
	    for fast in range(len(nums)):
	    	if nums[fast] != 0:
	    		nums[slow],nums[fast] = nums[fast],nums[slow]
	    		slow += 1        

66、加一

描述:给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。

class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        return list(map(int, list(str(int(''.join(map(str, digits)))+1))))

88、合并两个有序数组

描述:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
解题思路:从后往前汇总,并普通的归并排序类似。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int len1 = m - 1;
        int len2 = n - 1;
        int len = m + n - 1;
        while(len1 >= 0 && len2 >= 0) {
            // 注意--符号在后面,表示先进行计算再减1,这种缩写缩短了代码
            nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--];
        }
        // 表示将nums2数组从下标0位置开始,拷贝到nums1数组中,从下标0位置开始,长度为len2+1
        System.arraycopy(nums2, 0, nums1, 0, len2 + 1);
    }
}

189、旋转数组

描述:给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
思路:相对来说比较简单

# python技巧
# 超85%
class Solution:
    def rotate(self,nums,k):
        n = len(nums)
        k = k % n ## 避免不必要移动
        nums[:] = nums[n-k:] + nums[:n-k]
----
#python技巧
#超22%
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        k = k % len(nums)
        for i in range(k):
            temp = nums.pop()
            nums.insert(0,temp)
        return nums
---
## 大reverse保证整体数组将大端移动到了前面,小块内的reverse保证了数字顺序还是正序。
#反转数组
#超85%
class Solution:
    def reverse(self,nums,left, right):
        while left < right:
            nums[left], nums[right] = nums[right], nums[left]
            left += 1
            right -= 1
        return nums[添加链接描述](https://leetcode-cn.com/problems/move-zeroes/)
    def rotate(self, nums: List[int], k: int) -> None:
        k = k % len(nums)
        self.reverse(nums,0,len(nums)-1)
        self.reverse(nums,0,k-1)
        self.reverse(nums,k,len(nums)-1)
class Solution:
    def isLongPressedName(self, name: str, typed: str) -> bool:
        name = list(name)
        typed = list(typed)
        while name:
            c = name.pop()
            if not typed or typed.pop() != c:
                return False
            if not name or name[-1] != c:
                while typed and typed[-1] == c:
                    typed.pop()
        return not typed
---
双指针题目,快慢指针类型,注意边界条件的处理
class Solution:
    def isLongPressedName(self, name: str, typed: str) -> bool:
        l1, l2 = len(name), len(typed)
        i, j = 0, 0
        while j < l2:
            if i < l1 and name[i] == typed[j]:
                i += 1
                j += 1
            elif j > 0 and typed[j] == typed[j - 1]:
                j += 1
            else:
                return False
        return i == l1

链表

涉及到链表的操作,一定要在纸上把过程先画出来,再写程序

206、反转链表(经典)

解析:经典入门
思维:

我们可以申请两个指针,第一个指针叫 pre,最初是指向 null 的。
第二个指针 cur 指向 head,然后不断遍历 cur。
每次迭代到 cur,都将 cur 的 next 指向 pre,然后 pre 和 cur 前进一位。
都迭代完了(cur 变成 null 了),pre 就是最后一个节点了。

class Solution {
	public ListNode reverseList(ListNode head) {
		//申请节点,pre和 cur,pre指向null
		ListNode pre = null;
		ListNode cur = head;
		ListNode tmp = null;
		while(cur!=null) {
			//记录当前节点的下一个节点
			tmp = cur.next;
			//然后将当前节点指向pre
			cur.next = pre;
			//pre和cur节点都前进一位
			pre = cur;
			cur = tmp;
		}
		return pre;
	}
}

精辟的递归调用

class Solution {
	public ListNode reverseList(ListNode head) {
		//递归终止条件是当前为空,或者下一个节点为空
		if(head==null || head.next==null) {
			return head;
		}
		//这里的cur就是最后一个节点
		ListNode cur = reverseList(head.next);
		//这里请配合动画演示理解
		//如果链表是 1->2->3->4->5,那么此时的cur就是5
		//而head是4,head的下一个是5,下下一个是空
		//所以head.next.next 就是5->4
		head.next.next = head;
		//防止链表循环,需要将head.next设置为空
		head.next = null;
		//每层递归函数都返回cur,也就是最后一个节点
		return cur;
	}
}

19、删除链表的倒数第N个节点

  • 先计算总长度
  • 有了总长从前往后计算要删除点
class Solution:
    def removeNthFromEnd(self, head: ListNode, k: int) -> ListNode:
        # 1.计算链表的总长度n
        n = 0
        while head: # 此处的 p 只是为了求得总长度
            n += 1
            head = head.next
        # 2.因为可能删掉头结点,所以建立虚拟头结点dummy
        dummy = ListNode(-1)
        dummy.next = head
        # 3.找到倒数第k+1个结点的位置,倒数第1个结点是正数第n个结点,
        # 因此倒数第k+1个结点是正数第(n+1)-(k+1)=n-k
        p = dummy
        for i in range(n - k):
            p = p.next
        # 4.删除该节点
        p.next = p.next.next
        return dummy.next

20. 有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 注意空字符串可被认为是有效字符串。

21. 合并两个有序链表

简单递归:

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

迭代(跟归并排序类似):

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);
        ListNode prev = prehead; // 用一个节点来保存头节点!
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev.next = l1 == null ? l2 : l1;
        return prehead.next;
    }
}

23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
递归+ 合并

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

class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        if not lists:return 
        n = len(lists)
        return self.merge(lists, 0, n-1)
    def merge(self,lists, left, right):
        if left == right:
            return lists[left]
        mid = (right + left) // 2
        l1 = self.merge(lists, left, mid)
        l2 = self.merge(lists, mid+1, right)
        return self.mergeTwoLists(l1, l2)
    def mergeTwoLists(self,l1, l2):
        if not l1:return l2
        if not l2:return l1
        if l1.val < l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2

优先级队列:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
   public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        PriorityQueue<ListNode> queue = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                if (o1.val < o2.val) return -1;
                else if (o1.val == o2.val) return 0;
                else return 1;
            }
        });
        ListNode dummy = new ListNode(0);
        for (ListNode node : lists) {
            if (node != null) queue.add(node);
        }
        ListNode p = dummy;
        while (!queue.isEmpty()) {
            p.next = queue.poll(); // 从队列头部删除一个元素
            p = p.next;
            if (p.next != null) queue.add(p.next); // 一个链表的数据挨个处理,优先队列自动排序
        }
        return dummy.next;
    }
}

24. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
比如:
在这里插入图片描述
递归:
在这里插入图片描述

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    }
}

非递归:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode pre = new ListNode(0);
        pre.next = head;
        ListNode temp = pre;
        while(temp.next != null && temp.next.next != null) {
            ListNode start = temp.next;
            ListNode end = temp.next.next;
            temp.next = end;
            start.next = end.next;
            end.next = start;
            temp = start;
        }
        return pre.next;
    }
}

25. K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode hair = new ListNode(0);
        ListNode pre = hair;
        hair.next = head;
        
        while (head != null) {
            ListNode tail = pre;
            // 查看剩余部分长度是否大于等于 k
            for (int i = 0; i < k; ++i) {
                tail = tail.next;
                if (tail == null) {
                    return hair.next;
                }
            }
            ListNode nex = tail.next;
            ListNode[] reverse = myReverse(head, tail);
            head = reverse[0];
            tail = reverse[1];
            // 把子链表重新接回原链表
            pre.next = head;
            tail.next = nex;
            
            pre = tail;
            head = tail.next;
        }

        return hair.next;
    }

    public ListNode[] myReverse(ListNode head, ListNode tail) {
        ListNode prev = tail.next;
        ListNode p = head;
        while (prev != tail) {
            ListNode nex = p.next;
            p.next = prev;
            prev = p;
            p = nex;
        }
        return new ListNode[]{tail, head};
    }
}

61. 旋转链表

题意:给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
链表中的点已经相连,一次旋转操作意味着:

  • 先将链表闭合成环
  • 找到相应的位置断开这个环,确定新的链表头和链表尾
    在这里插入图片描述
class Solution {
  public ListNode rotateRight(ListNode head, int k) {
    // base cases
    if (head == null) return null;
    if (head.next == null) return head;
    // close the linked list into the ring
    ListNode old_tail = head;
    int n;
    for(n = 1; old_tail.next != null; n++)
      old_tail = old_tail.next;
    old_tail.next = head;
    // find new tail : (n - k % n - 1)th node
    // and new head : (n - k % n)th node
    ListNode new_tail = head;
    for (int i = 0; i < n - k % n - 1; i++)
      new_tail = new_tail.next;
      
    ListNode new_head = new_tail.next;
    // break the ring
    new_tail.next = null;
    return new_head;
  }
}

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

题意:给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
思路:利用好已经排序好这个条件,不断往后迭代考虑。

class Solution{
	public ListNode deleteDuplicates(ListNode head){
		ListNode dummy = new ListNode(0);
		ListNode cur = dummy;
		dummy.next = head ;
		// 因为需要 两个数据的对比 
		while(cur.next !=null && cur.next.next != null){
			if(cur.next.val == cur.next.next.val){
				ListNode temp = cur.next;
			while(temp.next !=null && temp.val == temp.next.val){
				temp = temp.next;
			}
			cur.next = temp.next;
			} else{
				cur = cur.next;
			}
		}
		return dummy.next;
	}
}

83. 删除排序链表中的重复元素,重复元素只出现一次。

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

114. 二叉树展开为链表

题意:给定一个二叉树,原地将它展开为一个单链表。

  • 从根开始遍历节点,如果该节点的左子树为空,那么可以直接遍历其右节点。
  • 如果左子树不为空,那么就需要把当前的右子树放到左子树的最后一个节点后面,也就是左子树的最右节点,然后将当前节点的rightright改为leftleft,leftleft置为空。同样继续遍历右节点。
class Solution {
    public void flatten(TreeNode root) {
        while(root != null) {
            if (root.left != null) {
                TreeNode pre = root.left;
                while(pre.right != null) pre = pre.right;
                pre.right = root.right;
                root.right = root.left;
                root.left = null;
            }
            root = root.right;
        }
    }
}

141. 环形链表

给定一个链表,判断链表中是否有环

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。如果链表中存在环,则返回 true 。 否则,返回 false 。

  • 遍历过的节点都存储到一个set中,如果发现有在set中的则出现了环。
public class Solution{
	public boolean hasCycle(ListNode head){
		Set<ListNode> seen = new HashSet<ListNode>();
		while(head != null){
			if(!seen.add(head)){
				return true;
			}
			head = head.next;
		}
		return fasle 
	}
}
  • 使用两个指针,一个快指针每次走两步,一个慢指针每次走一步,如果他们可以相遇,说明有环,如果遇到空说明遍历完成没有环。
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head, slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) return true;
        }
        return false;
    }
}

142. 环形链表 II,返回相交节点。

题解:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
思路

    public ListNode detectCycle(ListNode head) {
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            //快慢指针,快指针每次走两步,慢指针每次走一步
            fast = fast.next.next;
            slow = slow.next;
            //先判断是否有环,此时在环内某个点相交了。
            if (slow == fast) {
                //找环的入口,此处需用到 数据简单的公式推导。
                while (head != slow) {
                    //两相遇指针,一个从头结点开始,一个从相遇点开始每次走一步,直到再次相遇为止
                    head = head.next;
                    slow = slow.next;
                }
                return slow;
            }
        }
        return null;
    }

思路与算法:哈希表
一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}

143. 重排链表

题意:给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
方法一: 将数据遍历后 存入到一个数组中或者双向队列中,然后挨个取值。

class Solution {
    public void reorderList(ListNode head) {
        if (head == null) {
            return;
        }
        Deque<ListNode> deque = new LinkedList<>();
        ListNode next = head.next;
        while (next != null) {
            deque.add(next);
            next = next.next;
        }

        while (!deque.isEmpty()) {
            head.next = deque.pollLast();
            head = head.next;
            if (!deque.isEmpty()) {
                head.next = deque.pollFirst();
                head = head.next;
            }
        }
        head.next = null;
    }
}

方法二:寻找链表中点 + 链表逆序 + 合并链表

class Solution {
    public void reorderList(ListNode head) {
        if (head == null) {
            return;
        }
        ListNode mid = middleNode(head);
        ListNode l1 = head;
        ListNode l2 = mid.next;
        mid.next = null;
        l2 = reverseList(l2);
        mergeList(l1, l2);
    }

    public ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    public void mergeList(ListNode l1, ListNode l2) {
        ListNode l1_tmp;
        ListNode l2_tmp;
        while (l1 != null && l2 != null) {
            l1_tmp = l1.next;
            l2_tmp = l2.next;

            l1.next = l2;
            l1 = l1_tmp;

            l2.next = l1;
            l2 = l2_tmp;
        }
    }
}

160. 相交链表

编写一个程序,找到两个单链表相交的起始节点。
思路:如果两个链表相交,那么相交点之后的长度是相同的,让两个链表从同距离末尾同等距离的位置开始遍历这个位置只能是较短链表的头结点位置。为此,我们必须消除两个链表的长度差

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) return null;
    ListNode pA = headA, pB = headB;
    while (pA != pB) {
        pA = pA == null ? headB : pA.next;
        pB = pB == null ? headA : pB.next;
    }
    return pA;
}

203. 移除链表元素

删除链表中等于给定值 val 的所有节点。

class Solution {
  public ListNode removeElements(ListNode head, int val) {
    ListNode sentinel = new ListNode(0);
    sentinel.next = head;

    ListNode prev = sentinel, curr = head;
    while (curr != null) {
      if (curr.val == val) prev.next = curr.next;
      else prev = curr;
      curr = curr.next;
    }
    return sentinel.next;
  }
}

234. 回文链表

思路:这题是让判断链表是否是回文链表,所谓的回文链表就是以链表中间为中心点两边对称。我们常见的有判断一个字符串是否是回文字符串,这个比较简单,可以使用两个指针,一个最左边一个最右边,两个指针同时往中间靠,判断所指的字符是否相等。

第一种:1、找到中间点。2、逆序比较

public boolean isPalindrome(ListNode head) {
    ListNode fast = head, slow = head;
    //通过快慢指针找到中点
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
    }
    //如果fast不为空,说明链表的长度是奇数个
    if (fast != null) {
        slow = slow.next;
    }
    //反转后半部分链表
    slow = reverse(slow);

    fast = head;
    while (slow != null) {
        //然后比较,判断节点值是否相等
        if (fast.val != slow.val)
            return false;
        fast = fast.next;
        slow = slow.next;
    }
    return true;
}
//反转链表
public ListNode reverse(ListNode head) {
    ListNode prev = null;
    ListNode temp = null;
    while (head != null) {
        ListNode temp = head.next;
        head.next = prev;
        prev = head;
        head = temp;
    }
    return prev;
}

第二种:用栈先进后出的特性

public boolean isPalindrome(ListNode head) {
    ListNode temp = head;
    Stack<Integer> stack = new Stack();
    //把链表节点的值存放到栈中
    while (temp != null) {
        stack.push(temp.val);
        temp = temp.next;
    }

    //然后再出栈
    while (head != null) {
        if (head.val != stack.pop()) {
            return false;
        }
        head = head.next;
    }
    return true;
}

237. 删除链表中的节点

题意:请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点

public void deleteNode(ListNode node) {
    node.val = node.next.val;
    node.next = node.next.next;
}

876. 链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

class Solution {
    public ListNode middleNode(ListNode head)
    {
        ListNode fast = head;
        ListNode slow = head;
        while(fast !=null && fast.next !=null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

栈、队列

155、最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。

class MinStack {
    Deque<Integer> xStack; // 双端队列
    Deque<Integer> minStack;

    public MinStack() {
        xStack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        minStack.push(Integer.MAX_VALUE);
    }
    public void push(int x) {
        xStack.push(x);// 将元素压入此双端队列表示的堆栈上
        minStack.push(Math.min(minStack.peek(), x)); // peek = 检索队列的头部(但不删除)
    }
    public void pop() {
        xStack.pop();
        minStack.pop();
    }
    public int top() {
        return xStack.peek();
    }
    public int getMin() {
        return minStack.peek();
    }
}

20、有效的括号

题意:给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

思想:

如果 c 是左括号,则入栈 pushpush;
否则通过哈希表判断括号对应关系,若 stack 栈顶出栈括号 stack.pop() 与当前遍历括号 c 不对应,则提前返回 false。
字符串 s 以左括号结尾如何解决??

class Solution:
    def isValid(self, s: str) -> bool:
        dic = {'{': '}',  '[': ']', '(': ')', '?': '?'}
        stack = ['?'] # 字符串 s 以左括号结尾 
        for c in s:
            if c in dic: 
                stack.append(c)
            # 在迭代过程中,提前发现不符合的括号并且返回,提升算法效率
            elif dic[stack.pop()] != c: 
                return False 
        return len(stack) == 1

84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
思路:

我们可以遍历每根柱子,以当前柱子 i 的高度作为矩形的高,那么矩形的宽度边界即为向左找到第一个高度小于当前柱体 i 的柱体,向右找到第一个高度小于当前柱体 i 的柱体。
对于每个柱子我们都如上计算一遍以当前柱子作为高的矩形面积,最终比较出最大的矩形面积即可。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int area = 0, n = heights.length;
        // 遍历每个柱子,以当前柱子的高度作为矩形的高 h,
        // 从当前柱子向左右遍历,找到矩形的宽度 w。
        for (int i = 0; i < n; i++) {
            int w = 1, h = heights[i], j = i;
            while (--j >= 0 && heights[j] >= h) {
                w++;
            }
            j = i;
            while (++j < n && heights[j] >= h) {
                w++;
            }
            area = Math.max(area, w * h);
        }
        return area;
    }
}

在这里插入图片描述
单调栈

class Solution {
    public int largestRectangleArea(int[] heights) {
        // 这里为了代码简便,在柱体数组的头和尾加了两个高度为 0 的柱体。
        int[] tmp = new int[heights.length + 2];
        System.arraycopy(heights, 0, tmp, 1, heights.length); 
        
        Deque<Integer> stack = new ArrayDeque<>();
        int area = 0;
        for (int i = 0; i < tmp.length; i++) {
    // 对栈中柱体来说,栈中的下一个柱体就是其「左边第一个小于自身的柱体」;
    // 若当前柱体 i 的高度小于栈顶柱体的高度,说明 i 是栈顶柱体的「右边第一个小于栈顶柱体的柱体」。
    // 因此以栈顶柱体为高的矩形的左右宽度边界就确定了,可以计算面积
            while (!stack.isEmpty() && tmp[i] < tmp[stack.peek()]) {
                int h = tmp[stack.pop()];
                area = Math.max(area, (i - 1 - stack.peek() ) * h);   
            }
            stack.push(i);
        }
        return area;
    }
}

哈希

350、两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int i = 0, j = 0;
        int[] tmp = new int[nums1.length];
        int idx = 0;
        while(i < nums1.length && j < nums2.length){
            int a = nums1[i];
            int b = nums2[j];
            if(a == b){
                tmp[idx] = a;
                idx++;
                i++;
                j++;
            }else if(a < b){
                i++;
            }else{
                j++;
            }
        }
        return Arrays.copyOf(tmp, idx);
    }
}

Python 哈希表:

"""
哈希表
"""
from collections import Counter
class Solution:
    def intersect(self, nums1, nums2):
        nums1 = Counter(nums1)
        res = []
        for i in nums2:
            if i in nums1 and nums1[i]:
                res.append(i)
                nums1[i] -= 1
        return res

242、有效的字母异位词

题意:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

#哈希
#超96%
class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        return Counter(s) == Counter(t)

49、字母异位词分组

题意:给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串

#超76%
class Solution(object):
    def groupAnagrams(self, strs):
        ans = collections.defaultdict(list)	#default生成的字典,当在字典中访问的key值不存在时,不会发生访问错误,而是返回一个默认值,list就是返回一个空列表,也可以设置成int,返回0,设置成lamda,甚至设置一个函数,把函数名放到这个位置。
        for s in strs:
            ans[tuple(sorted(s))].append(s)	#字典的key只接受不可变类型,所以列表要转换成元组。sorted字符串的时候会自动打散成字母排序。
        return list(ans.values())	#字典的values要转换成list输出,别忘了values后面的括号

500、键盘行

给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词。键盘如下图所示。
在这里插入图片描述

class Solution():
    def findWords(self,words):
        set1 = set('qwertyuiop') #每行建立为一个集合
        set2 = set('asdfghjkl')
        set3 = set('zxcvbnm')
        result=[]
        for word in words: #取一组字符串中的一个字符串
            word_set = set(word.lower()) #字符存在大小写,set会将字符串分成一个个字符存储
            #判断字符串是否是某一行的子集
            if word_set.issubset(set1) or word_set.issubset(set2) or word_set.issubset(set3):
                result.append(word)
        return result
SoWhat1412 CSDN认证博客专家 CSDN签约作者 后端coder
微信搜索【SoWhat1412】,第一时间阅读原创干货文章。人之患、在好为人师、不实知、谨慎言。点点滴滴、皆是学问、看到了、学到了、便是收获、便是进步。
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值