一些在leetCode中高频面试热题中,针对各题型的简单基础题笔记。
冒泡排序
冒泡排序的基本思想:相邻的数据进行两两比较,较小的数放在前面,较大的数放在后面,这样一趟下来,最大的数就被排在最后,第二趟也是如此,如此类推,直到所有的数据排序完成。
上图演示的是快速排序的流程,可以看到每一轮排序都能将当前排序序列中最大的元素放到最后。
核心算法代码:
//冒泡排序
public static void bubbleSort(int[] arr) {
int n = arr.length;
//外部循环控制排序的趟数。冒泡排序的每一趟会将最大的元素"冒泡"到数组的末尾,因此需要执行 n-1 趟,其中 n 是元素的总个数
for (int i = 0; i < n - 1; i++) {
//内循环控制每趟比较的次数。由于每一趟都会将一个最大的元素沉到数组末尾,所以内循环次数逐渐减小。
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换arr[j]和arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
快速排序
快速排序的基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。具体流程如下:
从数组中挑出一个元素,称为 “基准”;
重新排序数列,所有元素比基准值小的摆放在基准左边,所有元素比基准值大的摆在基准的右边(从大到小排序则倒过来)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
上图演示的是以6为基准进行的一轮快速排序。
(图片来源:https://blog.csdn.net/MaYingBo928/article/details/130281848)
核心算法代码:
// 快速排序
public static void quickSort(int[] arr, int left, int right) { //这里的三个输入分别是待排序数组、开始排序的数组下标、结束排序的数组下标
if(left > right) {//当开始的下标大于结束的下标,就失去意义了直接return
return;
}
int temp = arr[left];// 基准点
int i = left;
int j = right;
while(i < j) {//从两个方向遍历数组
while(temp <= arr[j] && i<j) {//右测的遍历数据比基准大时则不改变位置
j--;
}
while(temp >= arr[i] && i<j) {//左测的遍历数据比基准小时则不改变位置
i++;
}
if(i<j) {//否则左侧遍历数据和右侧遍历数据互换位置
int m = arr[j];
arr[j] = arr[i];
arr[i] = m;
}
}
//以下两步是为了调整基准点的位置至数组中间
arr[left] = arr[j];
arr[j] = temp;
//对基准点两侧再进行快速排序
quickSort(arr,left,j-1);
quickSort(arr,j+1,right);
}
数组相关算法题
移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,需要在不复制数组的情况下原地对数组进行操作。
示例 :
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
解题思路: 此题可以用到类似于上文所提及的快速排序的思想,将0作为划分点,让不为0的数放在数组的左边,等于0的数全部放在数组的右边。具体流程为:设置a、b两个指针,a为当前还未处理的数组序列中的第一个数组下标,b为当前状态下数组序列中第一个值为0的数组下标。文字可能有点难理解,流程图如下:
核心算法代码:
public void moveZeroes(int[] nums) {
if(nums==null) { //如果输入等于空则直接返回
return;
}
//两个指针i和j
int i = 0;
int j = 0;
for(i=0;i<nums.length;i++) {
if(nums[i]!=0) {//当前元素!=0,就把其交换到左边,等于0的交换到右边
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
j++;
}
}
}
哈希相关算法题
两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现,可以按任意顺序返回答案。
示例:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,所以返回 [0, 1] 。
解题思路: 用哈希表来实现,因为hashMap的数据结构是kv键值对,可以把数组值作为key,当前值对应的下标作为value存进去(由于返回的是下标,所以下标为value)
核心算法代码:
//哈希表实现两数之和
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])) //当哈希表中包含了与当前数组值相加等于目标值的key,就返回对应的两个数组下标
{
return new int[]{map.get(target-nums[i]),i};
}
map.put(nums[i],i); //把当前数组值和对应下标放进哈希表
}
return null;
}
二分查找
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例1:
输入: nums = [1,3,5,6], target = 5
输出: 2(2为数组中值为5的下标,下标是从0开始计算的)
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1(数组中没有值为2的成员,所以插入在1和3之间,所以此时返回插入位置的下标为1)
解题思路: 利用二分查找来实现,二分查找的具体思想是:从数组中间(记为mid)开始搜索,如果此时正好等于目标值,则结束循环,返回值;否则比较需要查找的数和mid的大小关系,如果比mid大,则在mid右侧的子数组中再进行二分查找,如果比mid小,则在mid右侧进行二分查找。
核心算法代码:
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;//定义区间值
while(left <= right) {
int mid = (left + right)/2;//定义中间值的运算规则
if(nums[mid] == target) {//如果mid的值等于目标值
return mid;
}else if (nums[mid] < target) {//如果mid的值小于目标值
left = mid + 1;//在mid右侧继续二分查找
}else {//否则
right = mid -1;//在mid左侧继续二分查找
}
}
return left;//最后返回下标值
}
}
链表相关算法题
Java中链表的结构如下:
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
其中next是当前链表元素指向下一链表元素的指针,val是当前链表的元素值。
反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
解题思路:
将链表的next指针倒置,然后向后依次遍历链表。流程如下图所示:
核心算法代码:
public ListNode reverseList(ListNode head){
ListNode temp = null;
ListNode cur = head; //当前处理结点指针,初始为头结点
ListNode pre = null; //cur结点前一个结点的指针
while(cur!=null){
//将链表的next指针倒置,并向后遍历链表
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
判断链表是否有环
给你一个链表的头节点 head ,判断链表中是否有环。(如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。)
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例:
输入:head = [3,2,0,-4]
输出:true
解题思路: 可以利用Set集合元素不可重复的特点来解决问题。可以把已经访问过的结点存进HashSet中,每次到达一个结点就进行判断,如果HashSet中已经存在该节点,则说明该链表存在环。
核心算法代码:
public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<ListNode>(); //设置一个HashSet来存放已经访问过的链表结点
while (head != null) {
if (seen.add(head)==false) { //HashSet的add方法是用来向集合中添加元素的,它会有boolean类型的返回值,true则是添加成功,false则是添加失败,这里添加失败即为HashSet中已存在该结点
return true; //说明链表存在环
}
head = head.next;
}
return false;
}
栈结构相关算法题
栈是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素操作,即满足先进后出的条件。
栈相关的算法题一般只需了解java中的栈类(Stack类)中的常见方法使用即可。
empty( ):判断堆栈是否为空。
peek( ):返回栈顶端的元素,但不从堆栈中移除它。
pop( ):移除堆栈顶部的对象,并将该对象作为方法的返回对象。
push (Object element):把元素压入栈中。
search(Object element):返回对象在堆栈中的位置,它是以1为基数。
有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
示例:
输入:s = “()[]{}”
输出:true
解题思路: 设计一个辅助栈,将输入的字符串拆分成字符数组,逐个字符进行匹配判断:匹配到某类左括号时,将该类左括号的对应右括号字符push压入栈中;当匹配到右括号时,通过pop来判断此时栈顶元素是否与当前右括号相等,若相等则继续匹配,不相等则直接返回false。若到最后全部匹配完时,栈内元素全部出栈,则说明匹配成功,返回true。
由于栈先进后出的特性,左括号和右括号的相应匹配总能匹配到相距最近的。
例如:{ ( [ ] ) } 。对于这个字符串,当 { ( [ 被压进栈后,字符 “[” 对应的右括号 “]” 会在栈的栈顶,此时下一个右括号字符 “]” 即能与之匹配,以此类推。
核心算法代码:
public boolean isValid(String s) {
if(s.isEmpty()) //字符串为空则直接返回
return true;
Stack<Character> stack=new Stack<Character>(); //用辅助栈来解决该问题
for(char c:s.toCharArray()){ //将字符串转换为字符数组
if(c=='(')
stack.push(')');
else if(c=='{')
stack.push('}');
else if(c=='[')
stack.push(']');
else if(stack.empty()||c!=stack.pop())
return false;
}
if(stack.empty())
return true;
return false;
}