前言
陆陆续续刷算法题已有将近100道,近日回顾总结,发现许多题目已经不会做了。遂痛定思痛,改正懒惰癌,恢复之前记笔记的习惯。之后会陆续记录刷算法的个人进度(大概一天一题的样子)和一些开源框架源码的解读。
tips
文中的题都为leetcode原题,点击标题即可跳转原链接。这里只是个人小记,欢迎大家交流,有错误和更优解还望指出
数组双指针
1. 三数之和~~~~
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList();
Arrays.sort(nums);
for(int k=nums.length-1;k>=2;k--){
if(nums[k]<0) break;
int i=0,j=k-1;
while(i<j){
int target = -nums[k];
if(nums[i]+nums[j]==target){
result.add(Arrays.asList(nums[i],nums[j],nums[k]));
while(i<j&&nums[i]==nums[i+1]) i++;
while(j>i&nums[j]==nums[j-1]) j--;
i++;j--;
}else if(nums[i]+nums[j]<target){
i++;
}else{
j--;
}
}
while(k>=2&nums[k]==nums[k-1]) k--;
}
return result;
}
}
总结:三个数等于0,可以看做等价于两个数=0,假定一个数为target,另两个数的和为-target; 调整数组顺序,O(nlogn),丛数组指定一个最大的数从右向左平移,下标为k。运用双指针在在0~k-1的数组上从两侧向中间移动,找到所有等于-target,要注意边界判断和相等数略过。
2. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
class Solution {
public int threeSumClosest(int[] nums, int target) {
if(nums==null || nums.length<3) return -1;
Arrays.sort(nums);
int minGap =Integer.MAX_VALUE;
int closetsum=0;
for(int i=nums.length-1;i>=2;i--){
int j=0,k=i-1;
while(j<k){
int sum = nums[i]+nums[j]+nums[k];
if(sum==target){
return target;
} else if (sum<target){
while(j<k&&nums[j]==nums[j+1])j++;
j++;
}else{
while(j<k&&nums[k]==nums[k-1])k--;
k--;
}
int gap = Math.abs(sum-target);
if(minGap>gap){
minGap = gap;
closetsum = sum;
}
}
}
return closetsum;
}
}
总结:和上一题思路一致,只是多用了两个变量来记录最小差距和最小和。
3. 四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> resultList = new ArrayList();
if(nums==null||nums.length<4) return resultList;
Arrays.sort(nums);
for(int k=nums.length-1;k>=3;k--){
if(4*nums[k]<target) break;
for(int j =k-1;j>=2;j--){
if(3*nums[j]+nums[k]<target) break;
int newTarget = target - nums[k]-nums[j];
int x=0,y=j-1;
while(x<y){
if(newTarget==nums[x]+nums[y]){
resultList.add(Arrays.asList(nums[x],nums[y],nums[j],nums[k]));
while(x<y && nums[x]==nums[x+1]) x++;
while(x<y && nums[y]==nums[y-1]) y--;
x++;y--;
}else if (newTarget<nums[x]+nums[y]){
y--;
}else{
x++;
}
}
while(j>=2 && nums[j]==nums[j-1]) j--;
}
while(k>=3 && nums[k]==nums[k-1]) k--;
}
return resultList;
}
}
总结:给一个数组,求x个数之和等于一个target值或近似于一个值,求解思路都是一致的。整体时间复杂度O(n ^ x-1)。~~~~
4. 快乐数
编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
class Solution {
public boolean isHappy(int n) {
int fast=n,slow=n;
while(true){
fast = transform(transform(fast));
if(fast==1) return true;
slow = transform(slow);
if(fast == slow) return false;
}
}
public boolean isHappy2(int n) {
Set<Integer> set = new HashSet<Integer>();
while(true){
n = transform(n);
if(n==1) return true;
if(set.contains(n)) return false;
set.add(n);
}
}
public int transform(int n){
int result = 0;
while(n>0){
int temp = n %10;
result += temp * temp;
n=n/10;
}
return result;
}
}
总结:首先需要一个辅助函数transform,求出一个数每个位置上的平方和。两种解决方法:1.初始化一个容器set,记录每次的平方和,结果要么出现1是快乐数,要么在set中已经出现,则接下来是死循环不是循环数。2.可以参考判断循环链表的思路,慢指针一次走一步,快指针一次走两步(快指针相对速度为1),如快指针为1则为快乐数,如快指针和慢指针重合则链表中有环,则不为快乐数。
5. 快乐数
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。
class Solution {
//time:O(m+n) space:O(m+k)
public int[] intersect(int[] nums1, int[] nums2) {
if(nums1 == null || nums1.length ==0 || nums2 == null || nums2.length == 0 ) return new int[0];
//构造Map容器,用于记录第一个数组中每个数出现的次数
Map<Integer,Integer> statisticsMap = new HashMap();
for(Integer num : nums1){
if(statisticsMap.containsKey(num)){
statisticsMap.put(num,statisticsMap.get(num)+1);
}else{
statisticsMap.put(num,1);
}
}
//构造list容器,用来存放结果
//遍历数组2,和容器map比较,若重复则保存到list中
List<Integer> list = new ArrayList();
for(Integer num : nums2){
if(!statisticsMap.containsKey(num)) continue;
list.add(num);
if(statisticsMap.get(num)==1){
statisticsMap.remove(num);
}else{
statisticsMap.put(num,statisticsMap.get(num)-1);
}
}
int[] result = new int[list.size()];
for(int i=0;i<list.size();i++){
result[i] = list.get(i) ;
}
return result;
}
//time:O(m*log(m)+n*log(n)) space:O(k)
public int[] intersect2(int[] nums1, int[] nums2) {
if(nums1 == null || nums1.length ==0 || nums2 == null || nums2.length == 0 ) return new int[0];
Arrays.sort(nums1);
Arrays.sort(nums2);
List<Integer> list = new ArrayList();
int x = 0,y=0;
while(x<nums1.length&&y<nums2.length){
if(nums1[x]==nums2[y]){
list.add(nums1[x]);
x++;y++;
}else if(nums1[x]>nums2[y]){
y++;
}else{
x++;
}
}
int[] result = new int[list.size()];
for(int i=0;i<list.size();i++){
result[i] = list.get(i);
}
return result;
}
}
总结:两个数组的交集,2种解法。1:构造容器保存第一个数组每个元素出现的次数,与第二个数组进行比较,重复添加到结果容器list中;2:先对两个元素进行排序,然后运用双指针比较,若相等咋保存,且双指针+1,否则谁小谁+1,终止条件为任何一个数组达到角标边界。
6. 平方数之和
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c。
示例1:
输入: 5
输出: True
解释: 1 1 + 2 2 = 5
示例2:
输入: 3
输出: False
class Solution {
public boolean judgeSquareSum(int c) {
int end = (int) Math.sqrt(c);
for(int i=0;i<=end;i++){
int n = c- i * i;
if(isSquare(n)) return true;
}
return false;
}
public boolean isSquare(int c){
int temp = (int) Math.sqrt(c);
return temp *temp == c;
}
}
总结:先对数c进行求根得到end,若存在符合条件的两个数,一定在[0,end]区间内。从0开始遍历,用c减去遍历指针的平方得到结果值,若是一个平方数之和则c是平方数之和。
链表
1. 奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 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
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head ==null || head.next ==null) return head;
ListNode odd = head;
ListNode p2 = head.next;
ListNode head2 = p2;
ListNode subquentNode = p2.next;
for(int i=1;subquentNode!=null;i++){
if(i%2==1){
p1.next = subquentNode;
p1=p1.next;
}else{
p2.next = subquentNode;
p2=p2.next;
}
subquentNode = subquentNode.next;
}
p2.next =null;
p1.next = head2;
return head;
}
}
总结:链表的题大多都是要求空间复杂度为O(1),通过控制多个指针遍历达到最终结果。此题思路:记录奇数头结点、偶数头结点,从第三个节点开始遍历,若当前是奇数次循环则加在奇数链表尾部,否是偶数链表尾部。当节点为空,将奇数链表的尾节点的next指针指向偶数链表的头结点即可.
2. 对链表进行插入排序
对链表进行插入排序。
插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode insertionSortList(ListNode head) {
if(head == null || head.next==null) return head;
ListNode dummy = new ListNode(0),p=dummy;
ListNode cur = head,next;
while(cur!=null){
next = cur.next;
//针对每一个无序区元素,如果上一次有序区保留的p节点值大于无序区当前节点值,就无须重置p节点,可以省略掉一些不必要的比较。但不会影响具体时间间复杂度
if(p.next!=null&&cur.val<p.next.val) p=dummy;
while(p.next!=null&&cur.val>p.next.val)
p=p.next;
cur.next=p.next;
p.next=cur;
cur=next;
}
return dummy.next;
}
}
总结:首先看局部,将一个节点插入,需要前置节点和后置节点,才能保证不破坏链表。接着继续看插入排序,将链表分为有序区和无序区。有序区头结点为dummy(新创建的节点,因为链表插入需要持有前置节点),后续遍历比较使用指针p。无序区头结点为cur(从原链表头结点开始).只要cur节点不为空和有序区p.next不为空且p.next<cur,我们就将有序区p指针后移。否则我们将cur节点插入p和p.next(可能为null)之间,然后后移无序区cur指针,直到无序区没有元素,返回dummy.next;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。