刷题算法总结

算法
基础:二分;分治;贪心;倍增
动态规划
排序:快排;归并;计数
搜索:回溯;递归
数据结构
数组;字符串;链表;堆栈;二叉树

一. 二分法:

参考博客
时间复杂度按优劣排差不多集中在:
O(1), O(log n), O(n), O(n log n), O(n^2), O(n^k), O(2^n)
到目前位置,似乎我学到的算法中,时间复杂度是O(log n),好像就数二分查找法,其他的诸如排序算法都是 O(n log n)或者O(n2)。但是也正是因为有二分的 O(log n), 才让很多 O(n2)缩减到只要O(n log n)。

二分查找法主要是解决在“一堆数中找出指定的数”这类问题。
而想要应用二分查找法,这“一堆数”必须有一下特征:
存储在数组中
有序排列

所以如果是用链表存储的,就无法在其上应用二分查找法了。
至于是顺序递增排列还是递减排列,数组中是否存在相同的元素都不要紧。不过一般情况,我们还是希望并假设数组是递增排列,数组中的元素互不相同。

1. 二分查找法找目标元素
int bsearchWithoutRecursion(int array[], int low, int high, int target)
    {
        while(low <= high)  // 注意这里有=,因为low=high的时候的那个元素,没有经过判断,也需要去判断一下
        {
            int mid = (low + high)/2;
            if (array[mid] > target)
                high = mid - 1;  //因为那个元素已经比较过了,所以记得-1
            else if (array[mid] < target)
                low = mid + 1;
            else //find the target
                return mid;
        }
        // 数组中没有target
        return -1;
    }

注意:

  • 循环条件:是low <= high 注意等号=
  • 循环里面是mid-1mid+1

针对有重复元素的一些二分法的变形
参考

2. 查找第一个值等于给定值的元素
public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == 0) || (a[mid - 1] != value)) return mid;
      else high = mid - 1;
    }
  }
  return -1;
}

那当 a[mid]=value 的时候应该如何处理呢?
如果我们查找的是任意一个值等于给定值的元素,当 a[mid] 等于要查找的值时,a[mid] 就是我们要找的元素。但是,如果我们求解的是第一个值等于给定值的元素,当 a[mid] 等于要查找的值时,我们就需要确认一下这个 a[mid] 是不是第一个值等于给定值的元素

我们重点看第 11 行代码。如果 mid 等于 0,那这个元素已经是数组的第一个元素,那它肯定是我们要找的;如果 mid 不等于 0,但 a[mid] 的前一个元素 a[mid-1] 不等于 value,那也说明 a[mid] 就是我们要找的第一个值等于给定值的元素。

如果经过检查之后发现 a[mid] 前面的一个元素 a[mid-1] 也等于 value,那说明此时的 a[mid] 肯定不是我们要查找的第一个值等于给定值的元素。那我们就更新 high=mid-1,因为要找的元素肯定出现在 [low, mid-1] 之间。

3. 查找最后一个值等于给定值的元素
public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}

4. 查找第一个大于等于给定值的元素

在有序数组中,查找第一个大于等于给定值的元素。

public int bsearch(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid =  (low+high)/2;
            if (a[mid] >= value) {
                if ((mid == 0) || (a[mid - 1] < value)) return mid;
                else high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }
5. 查找第一个大于给定值的元素
public int bsearch(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid =  (low+high)/2;
            if (a[mid] > value) { //要判断这个是不是第一个大于value的值
                if ((mid == 0) || (a[mid - 1] <= value)) return mid;
                else high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }
6. 查找最后一个小于等于给定值的元素
public int bsearch7(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =   (low+high)/2;
    if (a[mid] > value) {
      high = mid - 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}
7. 查找最后一个小于给定值的元素
public int bsearch7(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =   (low+high)/2;
    if (a[mid] > value) {
      high = mid - 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}

二. 双指针法:

参考博客
双指针技巧再分为两类,一类是 「快慢指针」,一类是 「左右指针」
前者解决主要解决链表中的问题,比如典型的判定链表中是否包含环;
后者主要解决数组(或者字符串)中的问题,比如二分查找。

1. 快慢指针的常见算法

判定链表中是否含有环
经典解法就是用两个指针,一个跑得快,一个跑得慢。如果不含有环,跑得快的那个指针最终会遇到 null,说明链表不含环;如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。

boolean hasCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;

        if (fast == slow) return true;
    }
    return false;
}
2. 左右指针的常用算法

两数之和。
给定一个升序排序数组,找到两个数使得他们的和为target。
只要数组有序,就应该想到双指针技巧。这道题的解法有点类似二分查找,通过调节 left 和 right 可以调整 sum 的大小:

int[] twoSum(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left < right) {
        int sum = nums[left] + nums[right];
        if (sum == target) {
            // 题目要求的索引是从 1 开始的
            return new int[]{left + 1, right + 1};
        } else if (sum < target) {
            left++; // 让 sum 大一点
        } else if (sum > target) {
            right--; // 让 sum 小一点
        }
    }
    return new int[]{-1, -1};
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值