31. 下一个排列

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
示例 4:
输入:nums = [1]
输出:[1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
  • 解题
fun _0046_permute() {
println("--------_0046_permute-------")
println(permute(intArrayOf(1, 2, 3)))
}

/**
当 n=1 时,数组中只有一个数 a1,其全排列只有一种,即为 a1
当 n=2 时,数组中此时有 a1a2,其全排列有两种,a1a2 和 a2a1,
那么此时考虑和上面那种情况的关系,可以发现,其实就是在 a1 的前后两个位置分别加入了 a2
当 n=3 时,数组中有 a1a2a3,此时全排列有六种,分别为 a1a2a3, a1a3a2, a2a1a3, a2a3a1, a3a1a2, 和 a3a2a1。
那么根据上面的结论,实际上是在 a1a2 和 a2a1 的基础上在不同的位置上加入 a3 而得到的。
*/
fun permute(nums: IntArray): List<List<Int>> {
val res = LinkedList<List<Int>>()
res.add(ArrayList())
for (i in nums) {
for (k in res.size downTo 1) {
val item = res.pollFirst() //每次取出来一个并且弹出
for (j in 0..item.size) {
val temp: ArrayList<Int> = ArrayList(item)
temp.add(j, i)
res.add(temp)
}
}
}
return res
}

32. 最长有效括号

给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
  • 解题
fun _0032_longestValidParentheses() {
println("--------_0032_longestValidParentheses-------")
println(longestValidParentheses("(()"))
println(longestValidParentheses(")()())"))
println(longestValidParentheses("(()()())"))
println(longestValidParentheses("()()())"))
println(longestValidParentheses("(()()()"))
}

/**
借助栈来求解,需要定义个 start 变量来记录合法括号串的起始位置,遍历字符串,
如果遇到左括号,则将当前下标压入栈,
如果遇到右括号,如果当前栈为空,则将下一个坐标位置记录到 start,
如果栈不为空,则将栈顶元素取出,此时若栈为空,则更新结果和 i - start + 1 中的较大值,
否则更新结果和 i - st.peek() 中的较大值
*/
fun longestValidParentheses(s: String): Int {
var res = 0;
var start = 0;
val st = Stack<Int>()
for (i in s.indices) {
if (s[i] == '(') {
st.push(i)
} else if (s[i] == ')') {
if (st.empty()) {
start = i + 1
} else {
st.pop()
res = if (st.empty()) Math.max(res, i - start + 1) else Math.max(res, i - st.peek())
}
}
}
return res
}

33. 搜索旋转排序数组

给你一个整数数组 nums ,和一个整数 target 。
该整数数组原本是按升序排列,但输入时在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums 中的每个值都 独一无二
nums 肯定会在某个点上旋转
-10^4 <= target <= 10^4
  • 解题
fun _0033_search() {
println("--------_0033_search-------")
println(search(intArrayOf(4, 5, 6, 7, 0, 1, 2), 0))
println(search(intArrayOf(4, 5, 6, 7, 0, 1, 2), 3))
println(search(intArrayOf(1), 0))
println(search(intArrayOf(3, 1), 1))
println(search(intArrayOf(3, 1), 3))
}

/**
二分搜索法的关键在于获得了中间数后,判断下面要搜索左半段还是右半段,
如果中间的数小于最右边的数,则右半段是有序的,
若中间数大于最右边数,则左半段是有序的,
我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了
*/
fun search(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val mid = left + (right - left) / 2
if (nums[mid] == target)
return mid
if (nums[mid] < nums[right]) {
if (nums[mid] < target && nums[right] >= target)
left = mid + 1
else
right = mid - 1
} else {
if (nums[left] <= target && nums[mid] > target)
right = mid - 1
else
left = mid + 1
}
}
return -1
}

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
  • 解题
fun _0034_searchRange() {
println("--------_0034_searchRange-------")
println(searchRange(intArrayOf(5, 7, 7, 8, 8, 10), 10).contentToString())
println(searchRange(intArrayOf(5, 7, 7, 8, 8, 10), 9).contentToString())
println(searchRange(intArrayOf(5, 7, 7, 8, 8, 10), 8).contentToString())
println(searchRange(intArrayOf(5, 7, 7, 8, 8, 10), 7).contentToString())
println(searchRange(intArrayOf(5, 7, 7, 8, 8, 10), 6).contentToString())
println(searchRange(intArrayOf(5, 7, 7, 8, 8, 10), 5).contentToString())
}

/**
因为给的是按照升序排列的整数数组,使用两次二分查找法,第一次找到左边界,第二次调用找到右边界即可
*/
fun searchRange(nums: IntArray, target: Int): IntArray {
val res = intArrayOf(-1, -1)
var left = 0
var right = nums.size
while (left < right) {
val mid = left + (right - left) / 2
if (nums[mid] < target)
left = mid + 1
else
right = mid
}
if (right == nums.size || nums[right] != target)
return res
res[0] = right
right = nums.size
while (left < right) {
val mid = left + (right - left) / 2
if (nums[mid] <= target)
left = mid + 1
else
right = mid
}
res[1] = right - 1
return res
}

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
  • 解题
fun _0035_searchInsert() {
println("--------_0035_searchInsert-------")
println(searchInsert(intArrayOf(1, 3, 5, 6), 5))
println(searchInsert(intArrayOf(1, 3, 5, 6), 2))
println(searchInsert(intArrayOf(1, 3, 5, 6), 7))
println(searchInsert(intArrayOf(1, 3, 5, 6), 0))
}

/**
只需要遍历一遍原数组,若当前数字大于或等于目标值,则返回当前坐标,
如果遍历结束了,说明目标值比数组中任何一个数都要大,则返回数组长度n即可
可以用二分搜索法来优化时间复杂度
*/
fun searchInsert(nums: IntArray, target: Int): Int {
if (nums.last() < target) return nums.size
var left = 0
var right = nums.size
while (left < right) {
val mid = left + (right - left) / 2
if (nums[mid] < target)
left = mid + 1
else
right = mid
}
return right
}
我是今阳,如果想要进阶和了解更多的干货,欢迎关注公众号”今阳说“接收我的最新文章