您现在的位置是:首页 > 文章详情

leetcode算法题学习Java版(2)

日期:2018-10-01点击:421

283.Move Zeros(移动零)

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

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

class Solution { public void moveZeroes(int[] nums) { int k = 0; for(int i = 0;i<nums.length;i++){ if(nums[i]!=0){ if(i!=k){ int temp = nums[k]; nums[k] = nums[i]; nums[i] = temp; } k++; } } } } 

75.Sort Colors(颜色分类)

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:

  • 一个直观的解决方案是使用计数排序的两趟扫描算法。
    首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

解题思路,本题共三个元素,非常适合三路快排的方法,并且仅需扫描数组一遍即可完成排序

class Solution { public void sortColors(int[] nums) { int zero = -1; // nums[0...zero]==0 int two = nums.length; //nums[two...n-1]==2 for (int i = 0; i < two;) { if(nums[i]==0){ int temp = nums[++zero]; nums[zero] = nums[i]; nums[i++] = temp; }else if(nums[i]==1){ i++; }else{ assert nums[i]==2; int temp = nums[--two]; nums[two] = nums[i]; nums[i] = temp; } } } } 

88.Merged sorted Array(合并有序数组)

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]

解题思路:这道题解决很简单,但是最大效率的算法却也比较难想,很巧妙,暴力解法就是把两个数组合并,然后快排。但是参照了leetcode上别人的代码,效率更高,思路就是不断比较二者的最大值,把最大值放在nums1数组的最后,知道排序成功。

class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int count = m + n - 1; m--; n--; while (m != -1 && n != -1) { nums1[count--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--]; } while (n != -1) { nums1[count--] = nums2[n--]; } } } 

215.Kth Largest Element in an Array(数组中第K个最大的元素)

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

这道题很有趣,他的解法非常多,我也是在这道题感受到了自己与大佬之间的差距,就算用同一种解法,别人的代码也比我写的优雅的多。令人哭笑不得的是,自己写了半天的算法,其实运行下来比别人两行代码(Array.sort,调用jdk内置的函数)就搞定了还慢了很多倍。
解题思路:这道题用简单暴力的解法是非常容易的,同样是先对数组进行排序,最简单的办法是用jdk内置的Array.sort进行排序,然后从后往前取第k个元素,这种解法既简单,又由于jdk内置的快排效率非常高,在leetcode上这种解法只用了4ms,排名第二。当然更聪明的解法很多,例如,使用快排(分治算法),在快排的每一趟排完后,如果要找的k较大,则只对对应的一半继续进行快排,直到找到结果。还有使用堆的办法。

class Solution { public int findKthLargest(int[] nums, int k) { Queue<Integer> queue = new PriorityQueue<Integer>(); for (int i = 0; i < nums.length; i++) { if (i < k) { queue.add(nums[i]); } else if (queue.peek() < nums[i]) { queue.remove(); queue.add(nums[i]); } } return queue.peek(); } } 
class Solution { public int findKthLargest(int[] nums, int k) { int left = 0, right = nums.length - 1; while (true){ int pos = partition(nums, left, right); if(k - 1 == pos) return nums[pos]; else if(pos > k - 1) right = pos - 1; else left = pos + 1; } } public int partition(int[] nums, int left, int right){ int pivot = nums[left], l = left +1, r = right; while (l <= r){ if(nums[l] < pivot && nums[r] > pivot) swap(nums, l, r); if (nums[r] <= pivot) r--; if (nums[l] >= pivot) l++; } swap(nums, left, r); return r; } public void swap(int nums[], int left, int right){ int temp = nums[left]; nums[left] = nums[right]; nums[right] = temp; } } 

Two Sum II(两数之和)

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

解题思路:这道题同样有多种思路,暴力解法是两次遍历该数组,找到相加等于target的索引,这种解法的时间复杂度是O(n^2)。利用二分法的思想来解这道题,时间复杂度是O(nlogn)。而利用对撞指针来解这道题,时间复杂度只有O(n),以下是对撞指针的解法。

class Solution { public int[] twoSum(int[] numbers, int target) { int i = 0,j = numbers.length-1; while(i<j){ if(numbers[i]+numbers[j]==target){ break; } if(numbers[i]+numbers[j]>target){ j--; } if(numbers[i]+numbers[j]<target){ i++; } } return new int[]{++i,++j}; } } 

209.Minimum Size Subarray Sum(最小子数组)

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。

示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

解题思路:该题使用滑动窗口法解决只需要O(n)的时间复杂度

public class MSSubarraySum { public int minSubArrayLen(int s, int[] nums) { int i = 0, j = -1; int sum = 0; int result = nums.length+1; while(i<nums.length){ if(j<nums.length-1&&sum<s){ sum+=nums[++j]; }else{ sum-=nums[i++]; } if(sum>=s) result = Math.min(result,j-i+1); } if(result == nums.length+1){ result = 0; } return result; } } 

3.Longest Substring Without Repeating Characters(无重复字符的最小子串)

给定一个字符串,找出不含有重复字符的最长子串的长度。

示例 1:
输入: "abcabcbb"
输出: 3
解释: 无重复字符的最长子串是 "abc",其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 无重复字符的最长子串是 "b",其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 无重复字符的最长子串是 "wke",其长度为 3。
请注意,答案必须是一个子串,"pwke" 是一个子序列 而不是子串。

解题思路:这道题同样是使用滑动窗口的思想去解是最佳的。该题需要记录每个字符出现的频率,因此初始化一个长度为256的freq数组,根据字符的ASCII码来每个字符出现的频率。根据频率来进行窗口的滑动。

class Solution { public int lengthOfLongestSubstring(String s) { int[] freq = new int[256]; for (int f = 0; f < freq.length; f++) { freq[f] = 0; } int i = 0, j = -1; int result = 0; while (i < s.length()) { if (j < s.length() - 1 && freq[s.charAt(j + 1)] == 0) { freq[s.charAt(++j)]++; }else{ freq[s.charAt(i++)]--; } result = Math.max(result, j - i + 1); } return result; } } 

Minimum Window Substring(最小覆盖子串)

给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:
如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

解题思路:这道题我依然是使用滑动窗口的思想去解的,时间复杂度为O(2st),解题耗时较长,主要原因是每次都要遍历一遍结果集去判断结果是否正确,可以去参考leetcode 该题更好的解法。

class Solution { public String minWindow(String s, String t) { HashMap<Integer,Integer> target = new HashMap<>(); for(int i =0;i<t.length();i++){ if(target.get((int)t.charAt(i))==null){ target.put((int) t.charAt(i),1); }else{ int temp = target.get((int)t.charAt(i)); target.put((int) t.charAt(i),temp+1); } } int freq[] = new int[256]; for (int i = 0; i < freq.length; i++) freq[i] = 0; int i = 0, j = -1; int length = Integer.MAX_VALUE; String result = ""; while(i<s.length()){ if(j<s.length()-1&&!hasFound(freq,target)){ freq[s.charAt(++j)]++; }else{ freq[s.charAt(i++)]--; } if(hasFound(freq,target)){ if(length>j-i+1){ length = j-i+1; result = s.substring(i,j+1); } } } return result; } public static boolean hasFound(int[] arr1,HashMap<Integer,Integer> arr2){ // for(int i =0;i<arr2.length;i++){ // if(arr1[arr2[i]]<1) // return false; // } Iterator iter = arr2.entrySet().iterator(); while(iter.hasNext()){ Map.Entry<Integer,Integer> entry = (Map.Entry) iter.next(); if(arr1[entry.getKey()]<entry.getValue()){ return false; } } return true; } } 

451.Sort Characters By Frequency(根据字符出现频率排序)

示例 1:

输入:
"tree"

输出:
"eert"

解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

解题思路:这里我是用HashMap来解的,不过最后用了比较笨的办法来按顺序拼凑字符串。是通过类似选择排序的办法,每次比较map里的所有value值,将最大的值拿出来放在最前面。Leetcode上有更好的方法可以借鉴。

class Solution { public String frequencySort(String s) { Map<Character,Integer> map = new HashMap<>(); for(int i =0;i<s.length();i++){ map.put(s.charAt(i),map.getOrDefault(s.charAt(i),0)+1); } StringBuffer result = new StringBuffer(); int temp = -1; char tempKey=' '; int i = 0; while(i<s.length()){ for (Map.Entry<Character,Integer> entry:map.entrySet()) { if(temp<entry.getValue()){ tempKey = entry.getKey(); temp = entry.getValue(); } } map.remove(tempKey); while(temp>0){ result.append(tempKey); i++; temp--; } } return result.toString(); } } 

15.3Sum(三数之和)

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

解题思路:我用的是对撞指针的方式,遍历数组的过程中,对当前遍历的索引之后的数,采用对撞指针的方式,找到和等于0-nums【i】的两个数。我的代码略有瑕疵导致速度较慢,以下为leetcode上解题思路与我相同,但是做了细节优化的代码

class Solution { public List<List<Integer>> threeSum(int[] nums) { int len=nums.length; Arrays.sort(nums); List<List<Integer>> res = new ArrayList<>(); 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; } } 

454.4SumII(四数相加II)

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2

解释:
两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

解题思路:暴力解法为遍历四个数组所有情况,时间复杂度为O(n^4 )。本题我使用了查找表的方法,将数组A和B的所有和的情况置入查找表中,遍历C和数组D即可得到结果,时间复杂度为O(n^2)

public int fourSumCount(int[] A, int[] B, int[] C, int[] D) { int result = 0; Map<Integer,Integer> map = new HashMap<>(); for(int i = 0;i<A.length;i++){ for(int j = 0;j<B.length;j++){ map.put(A[i]+B[j],map.getOrDefault(A[i]+B[j],0)+1); } } for(int i = 0;i<C.length;i++){ for(int j = 0;j<D.length;j++){ if(map.containsKey(0-C[i]-D[j])){ result+=map.get(0-C[i]-D[j]); } } } return result; } 

206. Reverse Linked List 反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

解题思路:对于链表题来说,一般不能直接操作链表节点的值,而是通过操作指针来改变链表。例如,扫描一遍链表,将值压入栈中,再第二次扫描链表,将值从栈中弹出,这种方式一般在面试的算法题中是不被允许的。因此,这道题我用了三个临时指针,只改变listNode的next指针的方向来改变链表。

public class ReverseLinkedList { public ListNode reverseList(ListNode head) { if(head == null) return head; ListNode pre = null, cur = head, next = head.next; while (cur != null){ cur.next = pre; pre = cur; cur = next; if(next!=null){ next = next.next; } } return pre; } } 

92. Reverse Linked List II 反转链表 II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

这道题个人感觉还是挺难的,因为要处理的特殊情况较多,又不能直接更改节点的值,而是要通过指针来实现反转链表。这里还是以leetcode上大神写的更优美的代码为例吧

public ListNode reverseBetween_1(ListNode head, int m, int n) { if (head == null || m < 1 || m > n) return null; ListNode head1 = head; ListNode preTail = null;//需要逆置节点的前驱节点 //第一步:前进至第m节点,即行走m-1步;注意考虑m为1的情况,此时preTail=null int i = 1; while(head1 != null && i++ < m){ preTail = head1; head1 = head1.next; } //第二步:反转m->n节点 if(head1 == null) return head;//head1为null,不需要后翻转了 ListNode preNode = null; ListNode reverseListPreHead = head1; ListNode nextNode; while (head1 != null && m++ <= n) { nextNode = head1.next; head1.next = preNode; preNode = head1; head1 = nextNode; } //第三步:反转链表的头尾处理 reverseListPreHead.next = head1;//处理尾巴 if (preTail != null) {//处理头部 preTail.next = preNode; return head; }else{ return preNode;//若preTail为null,返回翻转链表段的最后一个节点 } } 

Remove Duplicates from Sorted List 删除排序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:

输入: 1->1->2
输出: 1->2
示例 2:

输入: 1->1->2->3->3
输出: 1->2->3

解题思路:这道题题意我理解错了,不知道是不是题目表达的问题,让我误以为要删除所有的不管是否相邻的具有重复值的节点。但看了leetcode上其他人的代码,都只是删除相邻的重复的节点。

 if(head ==null) return null; if(head.next == null) return head; Set<Integer> set = new HashSet<>(); ListNode temp = new ListNode(-1); temp.next = head; while (temp!=null&&temp.next!=null){ ListNode next = temp.next; if(set.contains(next.val)) temp.next = temp.next.next; else{ set.add(next.val); temp = temp.next; } } return head; 

86.Partition List 分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

示例:

输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5

解题思路:感觉链表的题都好难啊……但是看了别人的解法有感觉很简单,这道题的思路就是把小的和大的分别依次链起来,最后整合。

 public ListNode partition(ListNode head, int x){ ListNode head1 = new ListNode(0); ListNode head2 = new ListNode(0); ListNode phead1 = head1; ListNode phead2 = head2; ListNode temp = head; while (temp!=null){ if(temp.val<x){ phead1.next = temp; temp = temp.next; phead1 = phead1.next; phead1.next = null; }else{ phead2.next = temp; temp = temp.next; phead2 = phead2.next; phead2.next = null; } } phead1.next = head2.next; head = head1.next; return head; } 

328. Odd Even Linked List 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:

输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:

应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

解题思路:这道题和上一道题的解题思路基本一致。将奇偶数节点分别链起来,最后再整合即可。需要注意的是需要记录奇偶节点的首节点的位置,方便整合。

public ListNode oddEvenList(ListNode head) { ListNode temp = head; ListNode evenNode = new ListNode(-1); ListNode oddNode = new ListNode(-1); ListNode pEvenNode = evenNode; ListNode pfirOddNode = oddNode; int i = 1; while (temp != null){ if(i%2==0){ evenNode.next = temp; evenNode = evenNode.next; temp = temp.next; evenNode.next = null; }else { oddNode.next = temp; oddNode = oddNode.next; temp = temp.next; oddNode.next = null; } i++; } oddNode.next = pEvenNode.next; head = pfirOddNode.next; return head; } 

2.Add Two Numbers 两数相加

给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。

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

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

解题思路:不得不说看别人的代码都赏心悦目,相比之下自己的代码不知道是哪个弱智写的。这道题同样不难,两两相加,遇到某个数字位数不够了补0就可以了。这里还是以leetcode上大佬写的代码为例

public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode next = new ListNode(0); ListNode result = next; int add = 0; while (l1 != null || l2 != null) { int l1val = (l1 != null) ? l1.val : 0; int l2val = (l2 != null) ? l2.val : 0; int value = l1val + l2val + add; add = value / 10; next.next = new ListNode(value % 10); next = next.next; l1 = (l1 != null) ? l1.next : null; l2 = (l2 != null) ? l2.next : null; } if (add > 0) { next.next = new ListNode(add); } return result.next; } 

203. Remove Linked List Elements 移除链表元素

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

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

解题思路: 利用虚拟头结点,删除所有重复元素即可。我的代码有些小瑕疵,应该释放删除掉节点的内存空间。

public ListNode removeElements(ListNode head, int val) { ListNode dummyNode = new ListNode(0); dummyNode.next = head; ListNode cur = dummyNode; while (cur.next!=null){ if(cur.next.val==val) cur.next = cur.next.next; else cur = cur.next; } return dummyNode.next; } 

82. Remove Duplicates from Sorted List II 删除排序链表中的重复元素 II

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

示例 1:

输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:

输入: 1->1->1->2->3
输出: 2->3

解题思路:总感觉链表题都挺有难度的,可能是平常用的少的原因吧。这道题其实思路也很简单,就是遇到相同节点,就循环往下找下一个不同的节点,并链接。

public ListNode deleteDuplicates(ListNode head) { ListNode dummyHead = new ListNode(0); dummyHead.next = head; ListNode prev = dummyHead; while (prev.next != null) { ListNode cur = prev.next; if (cur.next == null || cur.val != cur.next.val) { prev = prev.next; continue; } while (cur.next != null && cur.next.val == cur.val) { cur.next = cur.next.next; } prev.next = cur.next; } return dummyHead.next; } 
原文链接:https://yq.aliyun.com/articles/653870
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章