设计一个超时功能
思路:
- 开一个watchdog线程,用来监控超时,如果超时通知外部
- 为提升程序性能,在等待超时的时间段内,watchdog线程要阻塞
- 超时链表管理可以放在伴生对象中实现
class ATimeOut constructor(private val timeoutNanos: Long) {
/** 记录超时时间点 */
private var timeoutAt = 0L
/** 下一个节点 */
private var next: ATimeOut? = null
/**
* 开始计时
*/
fun enter() {
val timeoutNanos = timeoutNanos
scheduleTimeout(this, timeoutNanos)
}
/**
* 通知使用方超时
*/
protected open fun timedOut() {}
/**
* 剩余时间
*/
private fun remainingNanos(now: Long) = timeoutAt - now
/**
* watchdog子线程
*/
private class Watchdog internal constructor() : Thread("watchdog") {
init {
isDaemon = true
}
override fun run() {
while (true) {
var timeout: ATimeOut? = null
synchronized(ATimeOut::class.java) {
/** 获取超时节点,等待期间线程阻塞 */
timeout = awaitTimeout()
// 链表为空,线程退出
// 下次schedule时,会创建一个新的线程
if (timeout === head) {
head = null
return
}
timeout?.timedOut()
}
}
}
}
/**
* 伴生对象负责:
* 1. 维护将新节点链入链表逻辑
* 2. 从链表中取出超时节点逻辑
*/
companion object {
private val IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)
private val IDLE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(IDLE_TIMEOUT_MILLIS)
/** 链表头 */
private var head: ATimeOut? = null
/**
* 将新节点链入链表当中
* 链表按超时时间排序
*/
private fun scheduleTimeout(node: ATimeOut, timeoutNanos: Long) {
synchronized(ATimeOut::class.java) {
if (head == null) {
head = ATimeOut(-1)
/** 开启watchdog线程 */
Watchdog().start()
}
val now = System.nanoTime()
node.timeoutAt = now + timeoutNanos
val remainingNanos = node.remainingNanos(now)
var prev = head!!
/** 链表按 超时时间排序 */
while (true) {
if (prev.next == null || remainingNanos < prev.next!!.remainingNanos(now)) {
node.next = prev.next
prev.next = node
if (prev === head) {
/** 唤醒watchdog线程 */
(ATimeOut::class.java as Object).notify()
}
break
}
prev = prev.next!!
}
}
}
/**
* 从链表取出超时节点,并从链表中将节点删除
*/
internal fun awaitTimeout(): ATimeOut? {
val node = head!!.next
if (node == null) {
val startNanos = System.nanoTime()
// 如果队列为空,watchdog线程等60s,60s后还为空,线程退出
(ATimeOut::class.java as Object).wait(IDLE_TIMEOUT_MILLIS)
// 线程被唤醒后,需要判断
return if (head!!.next == null && System.nanoTime() - startNanos >= IDLE_TIMEOUT_NANOS) {
head
} else {
null
}
}
var waitNanos = node.remainingNanos(System.nanoTime())
// 线程阻塞
if (waitNanos > 0) {
val waitMillis = waitNanos / 1000000L
waitNanos -= waitMillis * 1000000L
(ATimeOut::class.java as Object).wait(waitMillis, waitNanos.toInt())
return null
}
// 走到这里,代表节点超时,将节点删除
head!!.next = node.next
node.next = null
return node
}
}
}
两个链表想加
思路:
- 需要一个carry变量用于保存进位;
- 最后退出循环,还需再判断一下carry
public ListNode addLinkedList(ListNode l1, ListNode l2) {
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
// p,q两个移动指针
ListNode p = l1, q = l2;
int carry = 0;
while (p != null || q != null) {
int x = (p != null) ? p.val : 0;
int y = (q != null) ? q.val : 0;
int sum = carry + x + y;
// 计算进位
carry = sum / 10;
// 构建新节点,注意要和10取模
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (p != null) p = p.next;
if (q != null) q = q.next;
}
if (carry > 0) {
curr.next = new ListNode(carry);
}
return dummyHead.next;
}
查找无重复最长子串
思路:
- 需要两个指针,表示子串的左右边界
- 在查找过程中,借助数据结构Set存储子串并判断是否有发生重复
public int lengthOfLongestSubstring(String s) {
// 哈希集合,记录每个字符是否出现过
Set<Character> set = new HashSet<Character>();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int right = -1, result = 0;
int n = s.length();
for (int left = 0; left < n; left++) {
if (left != 0) {
// 左指针向右移动一格,移除一个字符
set.remove(s.charAt(left - 1));
}
while (right + 1 < n && !set.contains(s.charAt(right + 1))) {
// 不断地移动右指针
set.add(s.charAt(right + 1));
right++;
}
// 第 left 到 right 个字符是一个极长的无重复字符子串
result = Math.max(result, right - left + 1);
}
return result;
}
顺时针旋转矩阵
思路:
- 第一步:转置矩阵
- 第二步:每一行反转
public void rotate(int[][] matrix) {
int n = matrix.length;
// 转置
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int tmp = matrix[j][i];
matrix[j][i] = matrix[i][j];
matrix[i][j] = tmp;
}
}
// 反转每行
for (int i = 0; i < n; i++) {
int start = 0;
int end = n;
while (start < end) {
int tmp = matrix[i][start];
matrix[i][start] = matrix[i][end];
matrix[i][end] = tmp;
start++;
end--;
}
}
}
在数组中找三个数,其和最接近target
思路:
- 如果暴力查找,要三层循环
- 两层循环,利用双指针减少一层循环,前提需要排下序
public int threeCloseNumber(int[] nums, int target) {
// 排序
Arrays.sort(nums);
int result = nums[0] + nums[1] + nums[2];
for (int i = 0; i < nums.length - 2; i++) {
// 左右两个指针
int left = i + 1;
int right = nums.length - 1;
while (left != right) {
int sum = nums[0] + nums[left] + nums[right];
if (Math.abs(sum - target) < Math.abs(result - target)) {
result = sum;
}
// 如果比target大,右指针左移
if (sum > target) {
right--;
} else {
left++;
}
}
}
return result;
}
在数组中找出所有三元组,其和为0
思路:
- 最容易想到:三层循环,暴力查找
- 两层循环。需要借助辅助空间,暂存遍历过的数据
public List<List<Integer>> threeNum(int[] nums) {
// 存储所有三元组
Set<List<Integer>> result = new LinkedHashSet<>();
for (int i = 0; i < nums.length - 2; i++) {
int target = 0 - nums[i];
Map<Integer, Integer> map = new HashMap<>();
for (int j = i + 1; j < nums.length; j++) {
int v = target - nums[j];
Integer exist = map.get(v);
// 找到一个三元组
if (exist != null) {
List<Integer> list = Arrays.asList(nums[i], exist, nums[j]);
result.add(list);
} else {
// 存储数据
map.put(nums[j], nums[j]);
}
}
}
return result;
}