题目: 有n个人围成一圈,顺序排号。从第一个人开始报数报到第K个数,该人退出圈子,问最后留下的是原来第几号的那位。
例:1、2、3、4、5、6、7、8、9 K=3,结果1号留下
思路: 就是先构造一个环形链表,然后每次遍历到k
个数的时候,就删除那个数,然后模拟从下一个数开始报。时间复杂度 O(n * k)
。
例子: 1 -> 2 -> 3 -> 4
,k = 3
。
第一次删除3
。
第二次删除2
:
第三次删除4
,最后剩下1
:
代码:
/**
* 思路: 构造一个环形链表,每次删掉第k个。
* 然后指针从k+1继续围着环形链表走。
*/
public class Solution {
static class Node {
int val;
Node next;
Node(int v) {
val = v;
}
}
static int solve(int n, int k) {
if (k == 0 || n == 0)
return -1;
// 构造环形链表
Node head = new Node(1);
Node pre = head;
for (int i = 2; i <= n; i++) {
Node cur = new Node(i);
pre.next = cur;
pre = cur;
}
pre.next = head; // 构成环形
Node tail = pre;
int count = 0;
while (head != tail) {
if (++count == k) {
tail.next = head.next; // del head
head = tail.next;
count = 0;
} else {
tail = tail.next; // del tail
head = tail.next;
}
}
return head.val;
}
public static void main(String[] args){
System.out.println(solve(9, 3)); // out --> 1
}
}
时间复杂度还是O(n * m)
。
我们使用一个数组记录这个位置是否被删除了(-1
表示删除),直接模拟,每次统计count == m
,就删除当前移动到的位置,然后删除,然后找到下一个开始的位置。
代码:
public class Solution {
static int solve(int n, int k) {
if (n == 0 || k == 0) return -1;
int deleteNum = 0; // 已经删除的人的个数
int curPos = 1; // 当前位置
int[] a = new int[n + 1]; // 记录这个位置是否被删,如果是-1就是被删了
for (int i = 1; i <= n; i++) a[i] = i; // 一开始赋值为i
while (deleteNum != n - 1) { // 删完n-1个
int count = 1;
while (count < k) {
curPos = curPos % n + 1;
if (a[curPos] != -1) count++;
}
deleteNum++;
a[curPos] = -1; // 删除curPos位置的人
while (a[curPos] == -1) curPos = curPos % n + 1; //找到一下起点,如果从0开始就是curPos = (curPos + 1) % n
}
return a[curPos];
}
public static void main(String[] args) {
System.out.println(solve(4, 3)); // out --> 1
}
}
动态规划解法。
假设问题是从n个人编号分别为0...n-1
,每次取第k
个,则第k个人编号为k-1
的淘汰。
看当由n
个人淘汰第k
个变成n-1
个人之后重新编号的顺序。如下:
例如由最后一个人推到两个人: 由f[1] = 0
得到f[2]=(f[1]+k)%2
,这样就可以求出最终胜利者在2
个人的时候的情况下的编号。
总结递推公式f[n]=(f[n-1]+k)%n
,可递推到最初编号序列中该胜利者的编号。
不过由于要求编号从1开始,只要把f[n]+1即可。
代码:
public class Solution {
static int solve(int n, int k){
if(n == 0 || k == 0) return -1;
int s = 0;
for(int i = 2; i <= n; i++) s = (s + k) % i;
return s + 1;//因为是从1开始,所以返回s+1即可
}
public static void main(String[] args){
System.out.println(solve(9, 3)); // out --> 1
}
}
提供一个懒汉模式的单实例类实现。
要求:
1)、考虑线程安全。
2)、基于junit提供测试代码,模拟并发,测试线程安全性,给出对应的断言。
直接在newInstance()
上加上一把锁就是懒汉式的线程安全版本。在newInstance()
中休眠一段时间。如果不加锁,多个线程同时访问就存在问题。
这里我如果将Singleton
看做是封装的,在外面测,也能偶尔测试成功,但是需要尝试次数比较多。原因是因为newInstance()
方法执行时间过短,线程间切换概率小。
单例类:
public class Singleton {
private static Singleton uniqueInstance;
private Singleton(){
}
// 直接在 懒汉式 的方法上加上一把锁,这样才会线程安全, 但是性能一般
public static /*synchronized*/ Singleton newInstance(){
if(uniqueInstance == null) {
// 休眠, 来测试线程安全性
try {
TimeUnit.MILLISECONDS.sleep((int) (50 * Math.random()));
} catch (InterruptedException e) {
e.printStackTrace();
}
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
测试类:
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.*;
public class AppTest {
@Test
public void test() throws InterruptedException {
int threadCount = 20;
ExecutorService service = Executors.newFixedThreadPool(threadCount);
List<Future<Singleton>> futures = new ArrayList<>();
for (int c = 0; c < threadCount; c++) {
Future<Singleton> submit = service.submit(() -> {
// 如果不在Singleton中测,而是在这里测,比较难测出来,因为单例那个方法执行时间太短了
// TimeUnit.MILLISECONDS.sleep((int)(1000 * Math.random()));
return Singleton.newInstance();
});
futures.add(submit);
}
HashSet<Singleton> set = new HashSet<>();
for (Future<Singleton> future : futures) {
try {
set.add(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(set.size());
assertTrue(set.size() == 1);//断言
}
}
不加synchronized
的时候运行会出现:
还有一种实现方式,使用Semaphore
和CountDownLatch
实现:
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import java.util.HashSet;
import java.util.concurrent.*;
public class AppTest2 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
@Test
public void test()throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
HashSet<Singleton> set = new HashSet<>();
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
set.add(Singleton.newInstance());
semaphore.release();
} catch (Exception e) {
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(set.size());
assertTrue(set.size() == 1);
}
}