1. 两数之和
//空间换时间,利用map降低时间复杂度
fun twoSum(nums: IntArray, target: Int): IntArray {
val map = HashMap<Int, Int>()
nums.forEachIndexed { index, i ->
if (map.containsKey(target - i)) {
return intArrayOf(map[target - i]!!, index)
}
map[i] = index
}
return intArrayOf()
}
2. 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
fun addTwoNumbers(l1: ListNode?, l2: ListNode?): ListNode? {
var node1 = l1
var node2 = l2
var result: ListNode? = null
var tmp: ListNode? = null
var carry = 0 //进位
while (node1 != null || node2 != null) {
val value1 = node1?.`val` ?: 0
val value2 = node2?.`val` ?: 0
val sum = value1 + value2 + carry
val value = sum % 10
carry = sum / 10
if (result == null) {
tmp = ListNode(value)
result = tmp
} else {
tmp?.next = ListNode(value)
tmp = tmp?.next
}
node1 = node1?.next
node2 = node2?.next
}
if (carry > 0) {
tmp?.next = ListNode(carry)
}
return result
}
3. 无重复字符的最长子串
fun lengthOfLongestSubstring(content: String): Int {
val map = HashMap<Char, Int>()
var max = 0
var start = 0
content.forEachIndexed { index, c ->
if (map.containsKey(c)) {
start = maxOf(start, map[c]!! + 1) //更新起点位置,用max abba
}
max = maxOf(max, index - start + 1)
map[c] = index
}
return max
}
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
fun threeSum(nums: IntArray): List<List<Int>> {
// 过滤
if (nums.size < 3) return emptyList()
Arrays.sort(nums) //排序
if (nums[0] > 0) return emptyList()
val list = ArrayList<List<Int>>()
var left: Int
var right: Int
for (i in nums.indices) {
if (nums[i] > 0) break
if (i > 0 && nums[i] == nums[i - 1]) continue // 过滤重复元素
left = i + 1
right = nums.size - 1
while (left < right) {
val sum = nums[i] + nums[left] + nums[right]
when {
sum > 0 -> right-- // sum大于0,右指针左移,右元素变小
sum < 0 -> left++ // sum小于0,左指针右移,左元素变大
else -> {
list.add(listOf(nums[i], nums[left], nums[right]))
while (left < right && nums[left] == nums[left + 1]) left++ // 过滤左右指针重复元素
while (left < right && nums[right] == nums[right - 1]) right-- // 过滤左右指针重复元素
left++
right--
}
}
}
}
return list
}
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode temp = new ListNode(0, head);
ListNode slow = temp;
ListNode fast = temp;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return temp.next;
}
20. 有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
fun isValid(s: String): Boolean {
if (s.length % 2 != 0) return false
val map = mapOf(')' to '(', ']' to '[', '}' to '{')
val stack = Stack<Char>()
s.forEach { c ->
if (map.containsKey(c)) {
if (stack.isEmpty() || stack.peek() != map[c]) {
return false
}
stack.pop()
} else {
stack.push(c)
}
}
return stack.isEmpty()
}
26. 删除有序数组中的重复项
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
public int removeDuplicates(int[] nums) {
if (nums.length == 0) {
return 0;
}
//快慢指针,记录有效的index和当前遍历的index
int slow = 0, fast = 1;
while (fast < nums.length) {
if (nums[slow] != nums[fast]) {
slow++;
nums[slow] = nums[fast];
}
fast++;
}
return slow + 1; //有效index + 1,数组长度
}
83. 删除排序链表中的重复元素
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
public ListNode deleteDuplicates(ListNode head) {
ListNode cur = head;
while (cur != null && cur.next != null) {
if (cur.val == cur.next.val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
fun searchRange(nums: IntArray, target: Int): IntArray {
var left = 0
var right = nums.size - 1
while (left <= right) {
val index = (left + right) / 2
when {
nums[index] > target -> right = index - 1
nums[index] < target -> left = index + 1
else -> {
var l = index
var r = index
while (l > 0 && nums[l - 1] == nums[l]) l--
while (r < nums.size - 1 && nums[r + 1] == nums[r]) r++
return intArrayOf(l, r)
}
}
}
return intArrayOf(-1, -1)
}
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
fun searchInsert(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val mid = (left + right) / 2
when {
nums[mid] < target -> left = mid + 1
nums[mid] > target -> right = mid - 1
else -> return mid
}
}
return left
}
53. 最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。
fun maxSubArray(nums: IntArray): Int {
if (nums.isEmpty()) return 0
var pre = nums[0]
var maxSum = nums[0]
for (i in 1 until nums.size) {
// 前面的结果如果对后面没有贡献,则丢弃
pre = if (pre > 0) pre + nums[i] else nums[i]
maxSum = Math.max(maxSum, pre)
}
return maxSum
}
101. 对称二叉树
给你一个二叉树的根节点 root , 检查它是否轴对称。
输入: root = [1,2,2,3,4,4,3]
输出: true
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
return isSymmetric(root.left, root.right);
}
public boolean isSymmetric(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;
}
return left.val == right.val && isSymmetric(left.left, right.right) && isSymmetric(left.right, right.left);
}
102. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
fun levelOrder(root: TreeNode?): List<List<Int>> {
val result = ArrayList<ArrayList<Int>>()
if (root == null) return result
val queue = ArrayDeque<TreeNode>()
queue.add(root)
while (queue.isNotEmpty()) {
val innerList = ArrayList<Int>()
//这里是要一次性把这一层遍历完
for (i in 0 until queue.size) {
val node = queue.removeFirst()
innerList.add(node.`val`)
if (node.left != null) queue.add(node.left!!) //添加下一层
if (node.right != null) queue.add(node.right!!) //添加下一层
}
result.add(innerList)
}
return result
}
136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
使用位运算特点
/**
* and 全1为1
* or 有1为1
* xor 不同为1
*/
fun singleNumber(nums: IntArray): Int {
var num = 0
nums.forEach { num = num xor it }
return num
}
140、141 环形链表
//链表是否有环
public boolean hasCycle(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
//链表环的入口
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
while (true) {
if (fast == null || fast.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
143. 重排链表
给定一个单链表 L **的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
fun reorderList(head: ListNode?) {
head ?: return
val stack = Stack<ListNode>()
var node = head
while (node != null) {
stack.push(node)
node = node.next
}
var current = head
repeat(stack.size / 2) {
current ?: return
val pop = stack.pop()
val next = current!!.next
current!!.next = pop
pop.next = next
current = next
}
current?.next = null
}
160. 相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode nodeA = headA, nodeB = headB;
while (nodeA != nodeB) {
//一条链表遍历结束后,指向另一条链表的头节点开始遍历
//走过的路相同,最终在相交点相遇
nodeA = nodeA == null ? headB : nodeA.next;
nodeB = nodeB == null ? headA : nodeB.next;
}
return nodeA;
}
179. 最大数
给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。 注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
fun largestNumber(nums: IntArray): String {
val comparator = Comparator<Int> { a, b ->
var sa = 10 // a = 24, sa = 100
while (sa <= a) sa *= 10
var sb = 10 // b = 5, sb = 10
while (sb <= b) sb *= 10
// 计算拼接 524 或 245
return@Comparator (b * sa + a) - (a * sb + b)
//return@Comparator -"$a$b".compareTo("$b$a")
}
val array = nums.toTypedArray()
Arrays.sort(array, comparator)
if (array[0] == 0) return "0"
return StringBuilder().apply { array.forEach { append(it) } }.toString()
}
206. 反转链表
fun reverseList(head: ListNode?): ListNode? {
if (head?.next == null) {
return head
}
var pre: ListNode? = null
var cur = head
while (cur != null) {
val next = cur.next
cur.next = pre //每次将当前next指向pre
pre = cur //向右
cur = next //平移
}
return pre
}
215. 数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
fun findKthLargest(nums: IntArray, k: Int): Int {
var left = 0
var right = nums.size - 1
val position = nums.size - k
while (true) {
// 循环一次后,index左边都比index小,index右边都比index大
val index = partition(nums, left, right)
when {
index > position -> right = index - 1
index < position -> left = index + 1
else -> return nums[index]
}
}
}
//利用快速排序的分治思想
fun partition(nums: IntArray, left: Int, right: Int): Int {
var start = left
var end = right
val temp = nums[left]
while (start < end) {
while (nums[end] >= temp && start < end) end--
while (nums[start] <= temp && start < end) start++
if (start < end) {
val swap = nums[start]
nums[start] = nums[end]
nums[end] = swap
}
}
nums[left] = nums[start]
nums[start] = temp
return start
}
287. 寻找重复数
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。
fun findDuplicate(nums: IntArray): Int {
var min = 1
var max = nums.size - 1
while (min < max) {
val mid = (min + max) / 2
val count = nums.count { it <= mid }
if (count > mid) { //表示重复数在1..mid中
max = mid
} else {
min = mid + 1
}
}
return min
}
448. 找到所有数组中消失的数字
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入: [4,3,2,7,8,2,3,1]
输出: [5,6]
fun findDisappearedNumbers(nums: IntArray): List<Int> {
for (i in nums.indices) {
val newIndex = Math.abs(nums[i]) - 1
if (nums[newIndex] > 0) {
nums[newIndex] = nums[newIndex] * -1
}
}
val list = ArrayList<Int>()
for (i in nums.indices) {
if (nums[i] > 0) {
list.add(i + 1)
}
}
return list
}
724. 寻找数组的中心下标
给你一个整数数组 nums,请编写一个能够返回数组 “中心下标” 的方法。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心下标,返回 -1 。如果数组有多个中心下标,应该返回最靠近左边的那一个。
fun pivotIndex(nums: IntArray): Int {
if (nums.isNotEmpty()) {
for (index in nums.indices) {
var left = 0
var right = 0
nums.forEachIndexed { i, num ->
if (index < i) left += num
if (index > i) right += num
}
if (left == right) {
return index
}
}
}
return -1
}
fun pivotIndex(nums: IntArray): Int {
if (nums.isNotEmpty()) {
val sum = nums.sum()
var left = 0
nums.forEachIndexed { index, num ->
if (left + num + left == sum) {
return index
}
left += num
}
}
return -1
}
912. 排序数组
//快速排序
fun quickSort(nums: IntArray, start: Int, end: Int) {
if (start >= end) return
var left = start
var right = end
val temp = nums[left] // 默认左边第一位是基准数
while (left < right) {
// 从右开始找小于基准数的
while (nums[right] >= temp && left < right) right--
// 从左开始找大于基准数的
while (nums[left] <= temp && left < right) left++
// 交换左右目标数位置
if (left < right) {
val swap = nums[left]
nums[left] = nums[right]
nums[right] = swap
}
}
// 交换基准数位置,左边全部小于基准数,右边全部大于基准数
nums[start] = nums[left]
nums[left] = temp
quickSort(nums, start, left - 1)
quickSort(nums, left + 1, end)
}
/**
*原理:比较相邻的两个元素,将较大的元素交换至右端。
*思路:依次比较相邻的两个元素,将小数放前边,大数放后边;
*第一趟比较完成后,最后一个数一定是数组中最大的一个数,所以第二趟比较的时候最后一个数不参与比较,依次类推,每一趟比较次数-1。
*/
//冒泡排序
fun popSort(nums: IntArray) {
for (i in nums.indices) {
for (j in 0 until nums.size - 1 - i) {
if (nums[j] > nums[j + 1]) {
val temp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = temp
}
}
println(nums.contentToString())
}
}
//归并排序
fun mergeSort(arr: IntArray) {
if (arr.isEmpty() || arr.size == 1) {
return
}
mergeSort(arr, 0, arr.size - 1)
}
private fun mergeSort(arr: IntArray, left: Int, right: Int) {
if (left >= right) {
return
}
val mid = (left + right) / 2
mergeSort(arr, left, mid)
mergeSort(arr, mid + 1, right)
merge(arr, left, mid, right) //合并两个数组
}
private fun merge(arr: IntArray, left: Int, mid: Int, right: Int) {
val leftArr = IntArray(mid - left + 1)
val rightArr = IntArray(right - mid)
for (i in left..mid) {
leftArr[i - left] = arr[i] //填充数据
}
for (i in (mid + 1)..right) {
rightArr[i - (mid + 1)] = arr[i] //填充数据
}
var lIndex = 0
var rIndex = 0
var arrIndex = left //原数组指针
//比较两个小数组元素,填充原数字
while (lIndex < leftArr.size && rIndex < rightArr.size) {
if (leftArr[lIndex] < rightArr[rIndex]) {
arr[arrIndex++] = leftArr[lIndex++]
} else {
arr[arrIndex++] = rightArr[rIndex++]
}
}
//左边数字未比较完毕,复制到后边
while (lIndex < leftArr.size) {
arr[arrIndex++] = leftArr[lIndex++]
}
//右边数字未比较完毕,复制到后边
while (rIndex < rightArr.size) {
arr[arrIndex++] = rightArr[rIndex++]
}
}
插入排序
private static void sort(int[] arr) {
int length = arr.length;
if (length <= 1) {
return;
}
//默认左边第一个是有序的,依次从第二个开始,插入到前边有序的合适位置
for (int i = 1; i < length; i++) {
int j = i - 1; //已排序
int temp = arr[i];
while (j >= 0 && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
/*while (j >= 0 && arr[j] > arr[j + 1]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
j--;
}*/
}
}
堆排序
public static void sort(int[] arr) {
int n = arr.length;
// 构建大根堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 依次取出堆顶元素,放到数组末尾
for (int i = n - 1; i >= 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 重新构建大根堆
heapify(arr, i, 0);
}
}
private static void heapify(int[] arr, int n, int i) {
int largest = i; // 初始化最大值为根节点
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 找到最大值
if (left < n && arr[left] > arr[largest])
largest = left;
if (right < n && arr[right] > arr[largest])
largest = right;
// 如果最大值不是根节点,则交换根节点和最大值,并继续构建大根堆
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
剑指 Offer 15. 二进制中1的个数
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
public static int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n &= n - 1; //消除最低位的1
count++;
}
return count;
}
动态规划0-1背包问题
0-1背包算法是一种常见的动态规划算法,其解决的问题是:给定一组有价值、有重量的物品和一个固定大小的背包,如何在不超过背包容量的前提下,使得装入背包中的物品总价值最大。
具体来说,该算法可以分为以下步骤:
- 创建一个二维数组dp,其中dp[i][j]表示在前i个物品中,背包容量为j时的最大价值。
- 初始化第一行和第一列,即当物品数量为0或背包容量为0时,最大价值都为0。
- 遍历所有物品,计算是否能将该物品放入背包中,从而更新dp数组。
- 最终,dp数组的最后一个元素即为所求的最大价值。
public static int knapsack(int[] w, int[] v, int c) {
int n = w.length;
int[][] dp = new int[n + 1][c + 1]; //背包矩阵
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= c; j++) {
if (j < w[i - 1]) { //背包容量 < 当前物品重量
dp[i][j] = dp[i - 1][j]; //上次最优解
} else {
// 不放当前物品 放当前物品(减当前物品重量后的最优解 + 当前物品重量)
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1]);
}
print(dp);
System.out.println();
}
}
return dp[n][c];
}
递归求解N皇后
public class NQueens {
public static void main(String[] args) {
solveNQueens(4);
}
public static void solveNQueens(int n) {
int[] cols = new int[n];
placeQueen(0, n, cols);
}
public static void placeQueen(int row, int n, int[] cols) {
if (row == n) { //摆放完成
System.out.println(Arrays.toString(cols));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.print(((cols[i] == j) ? "Q" : ".") + " ");
}
System.out.println();
}
return;
}
for (int i = 0; i < n; i++) {
cols[row] = i; //尝试摆放第row行皇后在第i列
if (isValid(row, cols)) { //检查同列,对角
placeQueen(row + 1, n, cols);
}
}
}
public static boolean isValid(int row, int[] cols) {
for (int i = 0; i < row; i++) {
if (cols[i] == cols[row] || Math.abs(i - row) == Math.abs(cols[i] - cols[row])) {
return false;
}
}
return true;
}
}