算法与数据结构

1. 数组

1.1 二分查找写法

对于边界的选取和节点的更新有两种写法:

边界条件左节点更新右节点更新
左闭右闭,[left,right]left<=rightleft = mid+1right = mid-1
左闭右开,[left,right)left<rightleft = mid+1right = mid

对于二者的区别,主要是右指针指向的节点是否在考虑范围内:第一种在考虑范围内,第二种不在考虑范围内。

中值的计算方法:

int middle = left + ((right - left) >> 1);
public int search(int[] nums, int target) {
    //左闭右开 [l,r)
    int l = 0;
    int r = nums.length;
    //由于r不属于有效范围内,所以r==l时既查找完毕
    while (l < r) {
        int mid = l + ((r - l) >> 1);
        if (target == nums[mid]) {  //先将是否为寻找的数值判断一下
            return mid;
        } else if (target > nums[mid]) {
            l = mid + 1;  //数值在右边,让左边界为mid+1
        } else {
            r = mid;  //数值在左边,让r等于mid
        }
    }
    //走到这没返回说明没找到,返回-1
    return -1;
}

 变体:

搜索插入位置:

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 模板和原型类似,小于target<插入位置<大于target,由于插入位置为仅次于target的右边,所以直接将l更新到mid+1即可,最后返回l,等于直接返回

在排序数组中查找元素的第一个和最后一个位置 :

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

注意边界赋值条件,当寻找左边界时:右边的全不要(大于等于目标值,更新右节点,左边界赋值为右节点-1);寻找右边界时:左边的全不要(小于等于目标值,更新左节点,右边界赋值为左节点)。

x的平方根:

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

将问题转化为,求0-x间的哪个数的平方更接近x,将问题转化成平方的形式计算。

有效的完全平方数

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

和上一个变种类似

1.2 双指针:移动数组元素

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

值得一提的是双指针写法,有两种双指针,都从头开始和相向双指针

都从头开始:快指针必须走完数组长度

相向双指针:(前提条件:题目对移动后的数组元素位置不做要求)慢指针从0开始,快指针从后面开始,当两个指针相遇时停止。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.size() - 1;
        while (leftIndex <= rightIndex) {
            // 找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        return leftIndex;   // leftIndex一定指向了最终数组末尾的下一个元素
    }
};

变体

删除排序数组中的重复项(按序去重)

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

和上述题目类似,注意边界条件,while循环首先判断索引是否越界。

移动零

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

双指针,for循环遍历,交换元素。

比较含退格的字符串

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

和上一题类似,此题用栈操作更方便

思路2:倒叙双指针

有序数组的平方

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

普通解法:平方后排序

进阶解法:相向双指针,从头和尾向中间移动

1.3 滑动窗口:长度最小的子数组

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

是典型的滑动窗口,但是在写滑动窗口时,要使用for循环,滑动窗口的右侧每次更新1步,左侧使用while循环判断是否符合条件。

变体:

水果成篮:寻找包含两种元素的最长子序列

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

典型滑动窗口,但同时需要使用HashMap来记录窗口内的值和数量,在判断Map的size超出阈值时,可以利用左侧边界的节点一定在map里来进行数量上的判断,而不用多个if。

最小覆盖字串

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 滑动窗口,可以利用ASCLL的数组

1.4 矩阵打印:螺旋矩阵II

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 四个边界(上右下左依次打印,每打印完向中间逼近1)

变体:

螺旋矩阵

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

方法和上述类似,一圈一圈遍历,但是注意此矩阵不是方阵,所以每次要加一个判断是否退出循环 

2. 链表

2.1 两两交换链表中的节点

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

使用递归解法,出口是head==null;head.next==null,然后只关注前两个节点如何构件以及和后面的链表如何链接即可。

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

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

快慢指针一次扫描,都从newHead开始出发,n减到0快指针停止,再次一起出发,快指针下一个为空时,慢指针下一个节点就是要删除的。

2.3 链表相交

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

省空间费时间:双指针,记录步数,让长链表的先走插值步,然后一起走,相等返回

省代码(不省时间,时间复杂度mn,因为contains查找时间复杂度是O(n)):Set存储第一个链表的节点,然后第二个节点每个查询一次。

2.4 环形链表II找入口

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

快慢指针,循环条件:快指针和快指针next不为空,当快慢指针重合时跳出循环

让慢指针从头开始走,快指针从相遇的节点开始走,相遇返回。

3. 哈希表

哈希表形式:数组(适合题目设计的数字或字符种类数不多),HashSet

哈希表常用api:

HashSet
方法名方法参数方法返回值方法描述代码示例
add(E e)

e - 要添加

的元素

boolean - 如果元素

尚未存在于集合中,

则返回 true

将指定的元素添加到

集合中,如果元素已

经存在,则集合不改

变,并返回 false

boolean added =

set.add("element1");

remove(Object o)

o - 要移除

的元素

boolean - 如果元素

存在并被移除,则

返回 true

从集合中移除指定的

元素,如果元素不存

在,则集合不改变,

并返回 false

boolean removed =

set.remove("element1");

contains(Object o)

o - 要检查

是否存在

的元素

boolean - 如果元素

存在于集合中,则

返回 true

如果集合包含指定的

元素,则返回 true

boolean contains =

set.contains("element1");

size()

int - 集合中的元素

数量。

返回集合中元素数量。int size = set.size();
isEmpty()

boolean - 如果集

为空,则返回 true

如果集合不包含任何

元素,则返回 true

boolean isEmpty =

set.isEmpty();

clear()从集合移除所有元素。set.clear();
iterator()

Iterator<E> 在集合

上的迭代器。

返回一个在集合上的

迭代器,按照元素插

入的顺序迭代集合中

的元素。

Iterator<String> iterator

= set.iterator();

迭代器写法:

        ArrayList<String> practiceStr = new ArrayList<>();
        Collections.addAll(practiceStr, "apple", "cat", "dog", "big");
        Iterator<String> iterator = practiceStr.iterator();
        while (iterator.hasNext()){
            log.info(iterator.next());
        }

3.1 有效的字母异位词

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 哈希表遍历两个字符串解决,可以使用数组和ASCLL码代替哈希表

变体:

字母异位词分组

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 遍历字符串数组,两种处理方法:

1. 将每个字符串按字母顺序排序,然后将排序后的字符串作为key存储

2. 统计每个字符出现的次数,将其拼接作为key

注意:返回值可以使用return new ArrayList<List<String>>(hm.values());

3.2 两个数组的交集

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 两个HashSet遍历数组完成

变体:

两个数组交集Ⅱ(将重复的元素也返回)

一个HashSet+ArrayList即可完成,参考题目数据不大,小于1000,可以使用数组代替set。

进阶部分:

  • 如果给定的数组已经排好序呢?你将如何优化你的算法?
  • 如果 nums1 的大小比 nums2 小,哪种方法更优?
  • 如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

1. 排序号可以使用双指针,相等记录,同时后移,否则小的后移

2. 时间上排序<Hash,空间上排序>Hash

3. 一种是使用Hash,将nums1保存到哈希表,nums2只涉及到查询,每次加载一部分即可。另外一种是使用外部排序,在磁盘上进行排序,典型的是归并排序,然后使用双指针。

归并代码:

public class Msort {
	public static void main(String[] args) {
		int[] arrays = new int[] {1,5,4,3,2};
		sort(arrays,0,arrays.length - 1);
		System.out.print(Arrays.toString(arrays));
	}
	public static void sort(int[] arr, int left, int right) {
		int[] temp = new int[arr.length];
		if(left >= right) {
			return;
		}
		int mid = (left + right)/2; // 2
		sort(arr,left,mid); 
		sort(arr, mid + 1, right);
		int i = left;
		int j = mid + 1;
		int t = 0;
		while(i <= mid && j <= right) {
			if(arr[i] <= arr[j]) {
				temp[t] = arr[i];
				i++;
				t++;
			}else {
				temp[t] = arr[j];
				j++;
				t++;
			}
		}
		while(i <= mid) {
			temp[t] = arr[i];
			i++;
			t++;
		}
		while(j <= right) {
			temp[t] = arr[j];
			j++;
			t++;
		}
		int k = 0;
		int tempIndex = left;
		while(tempIndex <= right) {
			arr[tempIndex] = temp[k];
			k++;
			tempIndex++;
		}
	}
}

3.3 快乐数

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

该题有特性:
1. 最终得到数字1

2. 陷入循环

3. 越来越大,这种情况不会发生,三位数最大不超过243,四位数及以上每次计算会丢失一位精度 

 方法一:将每次计算的结果保存到HashSet中,遇到重复的不为1的直接返回false

方法二:快慢指针,快指针计算两次,慢指针计算一次,最终快慢指针会相遇,判断此时他们的值是否为1.

3.4 四数相加II

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 看到形如:A+B....+N=0的式子,要转换为(A+...T)=-((T+1)...+N)再计算,这个T的分割点一般是一半,特殊情况下需要自行判断。定T是解题的关键。

该题需要两次双层for循环来解决,第一次将前两个数组的每一对数值相加,存储和的值和次数。第二次双重for循环依次遍历即可。 

3.5 三数之和

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 暴力解法:O(n^3)

哈希表:O(n^2)+O(n),空间O(n^2)

双指针:先排序,然后双层for循环,使用尾指针向头逼近来代替第三层循环。

要注意,答案不能出现重复元组,所以每次的循环要先判断是否和上一次元素不同。

3.6 四数之和

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 该题要求的是符合条件的数组,而不是数量,所以不能使用两个双层for循环,可以使用两层for循环+相向双指针,记住两个外层循环要首先排除数值相等情况。

在指针位置确定后,首先判断是否满足条件,满足则保存结果并且将左右指针分别更新为不相等的下一个位置。如果不满足条件则直接更新左指针或右指针

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值