【LeetCode】线性表操作与双指针练习
线性表操作练习
双指针练习:移动零 283. 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
双层循环实现
class Solution {
public void moveZeroes(int[] nums) {
// 判断是否是0 如果是 就和后面的数交换 直到是最后一个数 是0就冒泡
for(int i = 0; i< nums.length; i++){
for(int j = i+1;j<nums.length;j++){
if(nums[i]==0){
int temp = nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
}
}
}
定义一个指针,保存非零元素,其他的补0实现
class Solution {
// 先把非0元素覆盖掉原数组的部分值,最后统一补0
public void moveZeroes(int[] nums) {
int j = 0;
for(int i=0;i<nums.length;i++){
if(nums[i]!=0){
nums[j]=nums[i];
j++;
}
}
// 最后一把补0
for(;j<nums.length;j++){
nums[j]=0;
}
}
// 循环过程中把非0元素和0交换
public void moveZeroes(int[] nums) {
int j = 0;
for(int i=0;i<nums.length;i++){
if(nums[i]!=0){
nums[j]=nums[i];
//循环过程中不断的写0
if(i!=j){
nums[i]=0;
}
j++;
}
}
}
}
删除数组中重复元素 26. 删除排序数组中的重复项
class Solution {
public int removeDuplicates(int[] nums) {
// 定义j为上个排序好的不重复的值
int j = 0;
for(int i=1;i<nums.length;i++){
if(nums[j]!=nums[i]){
j++;
nums[j]=nums[i];
}
}
return ++j;
}
}
双指针夹逼练习:盛水容器 11. 盛最多水的容器
class Solution {
// 双层循环法,计算每两个立柱之间的盛水量,获取最大值
public int maxArea(int[] height) {
int maxArea=0;
for(int i = 0;i<height.length-1;i++){
for(int j = i+1; j<height.length;j++){
int area = (j-i)* Math.min(height[i],height[j]);
maxArea = Math.max(maxArea,area);
}
}
return maxArea;
}
// 双指针法:结合题目特点,先保持宽最大,然后不断的缩小宽更新高度,计算盛水量获取最大值
// 移动指针的方式:每次将矮的柱子移动到下一个位置
public int maxArea(int[] height) {
int maxArea=0;
int i = 0;
int j = height.length -1;
while(i!=j){
int area = (j-i)*Math.min(height[i],height[j]);
maxArea=Math.max(maxArea,area);
if(height[i]>height[j]){
j--;
}else{
i++;
}
}
return maxArea;
}
}
递归的循环实现:爬楼器 70. 爬楼梯
class Solution {
// 递归实现 n只能到45左右 栈溢出
public int climbStairs(int n) {
if(n==1) return 1;
if(n==2) return 2;
return climbStairs(n-1) + climbStairs(n-2);
}
// 数组实现 a[n]=a[n-1]+a[n-2]
public int climbStairs(int n) {
if(n<3){
return n;
}
int num[] = new int[n+1];
num[1]=1;
num[2]=2;
for(int i=3;i<=n;i++){
num[i]=num[i-1] + num[i-2];
}
return num[n];
}
// 题目仅返回最终结果枚举数量,这里可以只做过程计数
// fn = f1 + f2
// f1 = f2
// f2 = fn
public int climbStairs(int n) {
if(n<3){
return n;
}
int f1 = 1;
int f2 = 2;
int fn = 0;
for(int i=3;i<=n;i++){
fn = f1+f2;
f1 = f2;
f2 = fn;
}
return fn;
}
}
两数之和 1. 两数之和
class Solution {
// 两层循环暴力求解
public int[] twoSum(int[] nums, int target) {
int result[] = new int[2];
for(int i = 0; i< nums.length -1; i++){
for(int j = i+1 ; j < nums.length; j++){
if(nums[i]+ nums[j] == target){
result[0]=i;
result[1]=j;
}
}
}
}
// <target - nums[i], i> 入Map,从Map中获取到index
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap();
for(int i = 0;i< nums.length;i++){
if(map.containsKey(target - nums[i])){
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i],i);
}
return new int[0];
}
// 使用双指针,当前实现无法走通,排序后的nums数组坐标就不对了
public int[] twoSum(int[] nums, int target) {
Arrays.sort(nums);
int i = 0, j = nums.length-1;
while(i!=j){
if(nums[i]>target) break;
int sum = nums[i]+nums[j];
if(sum < target){
while(i!=j && nums[i]==nums[++i]) i++;
} else if(sum>target){
while(i!=j && nums[j]==nums[--j]) j--;
} else {
return new int[]{i,j};
}
}
return new int[0];
}
// 双指针法 记录排序前的数组 双指针找到结果后 到排序前的数组中还原
// 注意特殊的用例 nums=[3,3] target=6
public int[] twoSum(int[] nums, int target) {
int m=0,n=0,k,board=0;
int[] res=new int[2];
int[] tmp1=new int[nums.length];
System.arraycopy(nums,0,tmp1,0,nums.length);
Arrays.sort(nums);
for(int i=0,j=nums.length-1;i<j;){
if(nums[i]+nums[j]<target)
i++;
else if(nums[i]+nums[j]>target)
j--;
else if(nums[i]+nums[j]==target){
m=i;
n=j;
break;
}
}
for(k=0;k<nums.length;k++){
if(tmp1[k]==nums[m]){
res[0]=k;
break;
}
}
for(int i=0;i<nums.length;i++){
if(tmp1[i]==nums[n]&&i!=k)
res[1]=i;
}
return res;
}
}
双指针法处理3数之和
class Solution {
// 三层循环
public List<List<Integer>> threeSum(int[] nums) {
Set<List<Integer>> result = new HashSet<List<Integer>>();
for(int i = 0;i<nums.length -2;i++){
for(int j=i+1;j<nums.length-1;j++){
for(int k=j+1;k<nums.length;k++){
if(nums[i]+nums[j]+nums[k]==0){
List<Integer> res = Arrays.asList(nums[i],nums[j],nums[k]);
Collections.sort(res);
result.add(res);
}
}
}
}
return new ArrayList(result);
}
// 哈希表法 待实现
public List<List<Integer>> threeSum(int[] nums) {
Set<List<Integer>> result = new HashSet<List<Integer>>();
Map<Integer,List<Integer>> map = new HashMap();
for(int i = 0;i< nums.length-1;i++){
for(int j=i+1;j<nums.length;j++){
if(map.containsKey(-nums[i]-nums[j])){
List<Integer> res = new ArrayList();
res.addAll(map.get(-nums[i]-nums[j]));
res.add(-nums[i]-nums[j]);
Collections.sort(res);
result.add(res);
}
map.put(-nums[i]-nums[j],Arrays.asList(nums[i],nums[j]));
}
}
}
// 双指针法
// 算法说明 https://leetcode-cn.com/problems/3sum/solution/3sumpai-xu-shuang-zhi-zhen-yi-dong-by-jyd/
public List<List<Integer>> threeSum(int[] nums) {
Set<List<Integer>> result = new HashSet<List<Integer>>();
Arrays.sort(nums);
for(int k=0;k<nums.length-2;k++){
if(nums[k]>0) break;
if(k>1 && nums[k]==nums[k-1]){
continue;
}
int i=k+1;
int j=nums.length-1;
while(i<j){
int sum = nums[k]+nums[i]+nums[j];
if(sum<0){
while(i<j && nums[i]==nums[++i]);
} else if(sum>0){
while(i<j && nums[j]==nums[--j]);
} else {
result.add(Arrays.asList(nums[k],nums[i],nums[j]));
while(i<j && nums[i]==nums[++i]);
while(i<j && nums[j]==nums[--j]);
}
}
}
return new ArrayList(result);
}
}
精选链表操作
单链表反转 206. 反转链表
Reverse a singly linked list.
Example:
Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL
class Solution {
/**
* 解题思路:
* prev定义作为前节点,同时作为新链的头结点
* pos不断的移动,保证原链表中的剩余数据仍然是一个链,不会丢失
* index作为临时变量,接受从原链中剪掉的数据节点,将其next指向prev,同时作为新链的头结点
*/
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode pos = head;
while(pos!=null){
ListNode index = pos;
pos=pos.next;
index.next = prev;
prev=index;
}
return prev;
}
/**
* 解题思路:
* pos不断的移动,保证原链表中的剩余数据仍然是一个链,不会丢失
* newHead构造新链,将原链中剪掉头部的数据不断的在新头部插入
* index作为临时变量,接受从原链中剪掉的数据节点
*/
public ListNode reverseList(ListNode head) {
ListNode newHead = head;
ListNode pos = head;
while(pos!=null){
ListNode index=pos;
pos=pos.next;
if(index == head){
newHead = index;
newHead.next =null;
} else{
index.next = newHead;
newHead = index;
}
}
return newHead;
}
}
按照数据删除节点 剑指 Offer 18. 删除链表的节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
/**
* 实现思路:
* 1)如果是头结点,直接把头结点往后挪一个,返回新的链
* 2)如果是尾结点,直接把为节点设置为空
* 3)如果是中间结点,把上一个结点的next指向下一个结点
*/
public ListNode deleteNode(ListNode head, int val) {
if(head.val ==val){
head=head.next;
return head;
}
ListNode pos = head;
while(pos!=null){
if(pos.next!=null){
if(pos.next.val == val){
pos.next=pos.next.next;
}
pos=pos.next;
} else{
pos=null;
}
}
return head;
}
/**
* 实现思路:使用哨兵结点
* 使用哨兵结点后,相当于指针可以往前挪一个位置
* 1)如果是头结点,直接把哨兵结点连到头结点的下一个结点
* 2)如果是尾结点,也可以将pre连到为节点的next(即null)
*/
public ListNode deleteNode(ListNode head, int val) {
ListNode guard = new ListNode(0);
ListNode pre = guard;
pre.next = head;
while(head!=null){
if(head.val==val){
pre.next = head.next;
}
head = head.next;
pre = pre.next;
}
return guard.next;
}
}
给出链表的中间某节点,删除他,返回链表 面试题 02.03. 删除中间节点
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
链表中环的检测 141. 环形链表
/**
* 解题思路:快慢指针,相遇则有环
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next ==null){
return false;
}
ListNode fast = head.next;
ListNode slow = head;
while(slow!=fast){
if(fast==null||fast.next==null){
return false;
}
fast = fast.next.next;
slow = slow.next;
}
return true;
}
}
/**
* 解题思路:朴素思路
*/
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> visited = new HashSet<>();
while(head!=null){
if(!visited.add(head)){
return true;
}
head=head.next;
}
return false;
}
}
环形链表进阶142. 环形链表 II 找到入环结点
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> visited = new HashSet();
ListNode pos = head;
while(pos!=null) {
if(visited.contains(pos)){
return pos;
}
visited.add(pos);
pos = pos.next;
}
return null;
}
// 同一起点,相遇时,快走路程时慢走路程的2倍
// a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)⟹起点走到入环点和相遇点走到入环点的距离相等
// ==>
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
//如果快指针走到尽头,没环
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
ListNode start = head;
while(slow != start){
start = start.next;
slow = slow.next;
}
return start;
}
}
return null;
}
}
两个有序的链表合并 21. 合并两个有序链表
class Solution {
/**
* 解题思路:
* 循环链表l1和l2的每个节点,比较
* 如果l1的节点比l2的小,就插入到新链中去,同时把l1的节点往下走,反之亦然
* 最后如果某个链还有剩余节点,就表示是大的,直接追加到新链后面去
*/
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode newHead = new ListNode();
ListNode index = newHead;
while(l1!=null && l2!=null){
ListNode temp1 = l1;
ListNode temp2 = l2;
if(temp1.val<=temp2.val){
index.next = temp1;
index = index.next;
l1 = l1.next;
} else {
index.next = temp2;
index = index.next;
l2 = l2.next;
}
}
if(l1!=null){
index.next = l1;
} else {
index.next = l2;
}
return newHead.next;
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode();
ListNode temp = dummy;
ListNode pos1 = l1;
ListNode pos2 = l2;
while(pos1!=null && pos2!=null){
if(pos1.val < pos2.val){
temp.next = pos1;
pos1 = pos1.next;
} else {
temp.next = pos2;
pos2 = pos2.next;
}
temp = temp.next;
}
if(pos1!=null){
temp.next = pos1;
} else {
temp.next = pos2;
}
return dummy.next;
}
}
找到链表倒数第n个节点 剑指 Offer 22. 链表中倒数第k个节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
/**
* 解题思路:朴素思路
* 先遍历,然后算n-k+1
*/
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode pos = head;
int count = 0;
while(pos!=null){
count++;
pos = pos.next;
}
int seq = count -k +1;
pos = head;
for(int i=1;i<seq;i++){
pos = pos.next;
}
return pos;
}
/**
* 解题思路:双指针法
* 两个指针,第一个先走k-1步,第二个保持在头上,这样第一个走到头,第二个正好在到时第k个
*/
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode pos = head;
ListNode fast = head;
for(int i=0;i<k-1;i++){
fast = fast.next;
}
while(fast.next!=null){
fast=fast.next;
pos=pos.next;
}
return pos;
}
}
删除链表倒数第n个结点
求链表的中间结点 876. 链表的中间结点
/**
* 解题思路:快慢指针,一1一2
*/
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;
}
}
/**
* 解题思路:朴素算法
*/
class Solution {
public ListNode middleNode(ListNode head) {
ListNode pos = head;
int count = 0;
while(pos!=null){
count++;
pos = pos.next;
}
double middle = count / 2.0 +0.5;
int seq = new Double(Math.ceil(middle)).intValue();
pos = head;
for(int i=1;i<seq;i++){
pos = pos.next;
}
return pos;
}
}
两两交换指针节点24. 两两交换链表中的节点
class Solution {
// 题解参考
// https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/liang-liang-jiao-huan-lian-biao-zhong-de-jie-di-91/
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode();
dummy.next = head;
ListNode temp = dummy;
while (temp.next != null && temp.next.next != null) {
ListNode node1 = temp.next;
ListNode node2 = temp.next.next;
temp.next = node2;
node1.next = node2.next;
node2.next = node1;
temp = node2.next;
}
return dummy.next;
}
}
k个节点一组,逆序 25. K 个一组翻转链表
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode();
dummy.next = head;
// pre固定k节点逆序前的指针,这样k逆序后还能和前面的连上
ListNode pre = dummy;
// end固定k节点的结尾,要断掉结尾
ListNode end = dummy;
while(end.next!=null){
for (int i = 0; i < k && end != null; i++) end = end.next;
if (end == null) break;
// start固定每次k逆序的头节点
ListNode start = pre.next;
// next记录断尾的后一个节点,方便后面连上
ListNode next = end.next;
end.next = null;
pre.next = reverseList(start);
// 逆序后start是k的最后一个节点,需要链接到next
start.next = next;
pre = start;
end = pre;
}
return dummy.next;
}
private ListNode reverseList(ListNode head){
ListNode newHead = null;
ListNode pos = head;
while(pos!=null){
ListNode temp = pos;
pos=pos.next;
temp.next = newHead;
newHead = temp;
}
return newHead;
}
}

浙公网安备 33010602011771号