Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

原来一直有点被误导,
gatingSequences用来标识每个 processer 的操作位点,但是怎么记录更新有点搞不清楚
其实问题在于 gatingSequences 是个 Sequence 数组,首先要看下怎么加进去的,
可以看到是在 com.lmax.disruptor.RingBuffer#addGatingSequences 这个方法里添加
首先是 com.lmax.disruptor.dsl.Disruptor#handleEventsWith(com.lmax.disruptor.EventHandler<? super T>...)
然后执行 com.lmax.disruptor.dsl.Disruptor#createEventProcessors(com.lmax.disruptor.Sequence[], com.lmax.disruptor.EventHandler<? super T>[])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
EventHandlerGroup<T> createEventProcessors(
final Sequence[] barrierSequences,
final EventHandler<? super T>[] eventHandlers)
{
checkNotStarted();

final Sequence[] processorSequences = new Sequence[eventHandlers.length];
final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);

for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
{
final EventHandler<? super T> eventHandler = eventHandlers[i];

// 这里将 handler 包装成一个 BatchEventProcessor
final BatchEventProcessor<T> batchEventProcessor =
new BatchEventProcessor<>(ringBuffer, barrier, eventHandler);

if (exceptionHandler != null)
{
batchEventProcessor.setExceptionHandler(exceptionHandler);
}

consumerRepository.add(batchEventProcessor, eventHandler, barrier);
processorSequences[i] = batchEventProcessor.getSequence();
}

updateGatingSequencesForNextInChain(barrierSequences, processorSequences);

return new EventHandlerGroup<>(this, consumerRepository, processorSequences);
}

BatchEventProcessor 在类内有个定义 sequence

1
private final Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);

然后在上面循环中的这一句取出来

1
processorSequences[i] = batchEventProcessor.getSequence();

调用com.lmax.disruptor.dsl.Disruptor#updateGatingSequencesForNextInChain 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
private void updateGatingSequencesForNextInChain(final Sequence[] barrierSequences, final Sequence[] processorSequences)
{
if (processorSequences.length > 0)
{
// 然后在这里添加
ringBuffer.addGatingSequences(processorSequences);
for (final Sequence barrierSequence : barrierSequences)
{
ringBuffer.removeGatingSequence(barrierSequence);
}
consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences);
}
}

而如何更新则是在处理器 com.lmax.disruptor.BatchEventProcessor#run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void run()
{
if (running.compareAndSet(IDLE, RUNNING))
{
sequenceBarrier.clearAlert();

notifyStart();
try
{
if (running.get() == RUNNING)
{
processEvents();
}
}
finally
{
notifyShutdown();
running.set(IDLE);
}
}
else
{
// This is a little bit of guess work. The running state could of changed to HALTED by
// this point. However, Java does not have compareAndExchange which is the only way
// to get it exactly correct.
if (running.get() == RUNNING)
{
throw new IllegalStateException("Thread is already running");
}
else
{
earlyExit();
}
}
}

然后是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private void processEvents()
{
T event = null;
long nextSequence = sequence.get() + 1L;

while (true)
{
try
{
final long availableSequence = sequenceBarrier.waitFor(nextSequence);
if (batchStartAware != null)
{
batchStartAware.onBatchStart(availableSequence - nextSequence + 1);
}

while (nextSequence <= availableSequence)
{
event = dataProvider.get(nextSequence);
eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
nextSequence++;
}
// 如果正常处理完,那就是会更新为 availableSequence,因为都处理好了
sequence.set(availableSequence);
}
catch (final TimeoutException e)
{
notifyTimeout(sequence.get());
}
catch (final AlertException ex)
{
if (running.get() != RUNNING)
{
break;
}
}
catch (final Throwable ex)
{
handleEventException(ex, nextSequence, event);
// 如果是异常就只是 nextSequence
sequence.set(nextSequence);
nextSequence++;
}
}
}

入口是可以在 springboot 的启动类上打上EnableApolloConfig注解

1
2
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {

这个 import 实现了

1
2
3
4
5
6
7
8
9
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {

private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class);

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
helper.registerBeanDefinitions(importingClassMetadata, registry);
}
}

然后就调用了

1
com.ctrip.framework.apollo.spring.spi.DefaultApolloConfigRegistrarHelper#registerBeanDefinitions

接着是注册了这个 bean,com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
String[] namespaces = attributes.getStringArray("value");
int order = attributes.getNumber("order");
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);

Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
propertySourcesPlaceholderPropertyValues.put("order", 0);

BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
// 注册了这个 bean
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
PropertySourcesProcessor.class);

而com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor 实现了 org.springframework.beans.factory.config.BeanFactoryPostProcessor
它里面的 com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor#postProcessBeanFactory 方法就会被 spring 调用,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void initializePropertySources() {
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized
return;
}
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);

//sort by order asc
ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
Iterator<Integer> iterator = orders.iterator();

while (iterator.hasNext()) {
int order = iterator.next();
for (String namespace : NAMESPACE_NAMES.get(order)) {
// 这里获取每个 namespace 的配置
Config config = ConfigService.getConfig(namespace);

composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
}

然后是 com.ctrip.framework.apollo.ConfigService#getConfig
接着就是它
com.ctrip.framework.apollo.internals.DefaultConfigManager#getConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public Config getConfig(String namespace) {
Config config = m_configs.get(namespace);

if (config == null) {
synchronized (this) {
config = m_configs.get(namespace);

if (config == null) {
ConfigFactory factory = m_factoryManager.getFactory(namespace);

// 通过 factory 来创建配置获取
config = factory.create(namespace);
m_configs.put(namespace, config);
}
}
}

创建配置

1
2
3
4
5
6
7
8
9
10
com.ctrip.framework.apollo.spi.DefaultConfigFactory#create
@Override
public Config create(String namespace) {
ConfigFileFormat format = determineFileFormat(namespace);
if (ConfigFileFormat.isPropertiesCompatible(format)) {
return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
}
// 调用到这
return new DefaultConfig(namespace, createLocalConfigRepository(namespace));
}

然后

1
2
3
4
5
6
7
8
9
10
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
if (m_configUtil.isInLocalMode()) {
logger.warn(
"==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",
namespace);
return new LocalFileConfigRepository(namespace);
}
// 正常会走这个,因为要从配置中心获取
return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
}

然后是创建远程配置仓库

1
2
3
4
com.ctrip.framework.apollo.spi.DefaultConfigFactory#createRemoteConfigRepository
RemoteConfigRepository createRemoteConfigRepository(String namespace) {
return new RemoteConfigRepository(namespace);
}

继续对当前的 namespace 创建远程配置仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
com.ctrip.framework.apollo.internals.RemoteConfigRepository#RemoteConfigRepository
public RemoteConfigRepository(String namespace) {
m_namespace = namespace;
m_configCache = new AtomicReference<>();
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);
m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
m_longPollServiceDto = new AtomicReference<>();
m_remoteMessages = new AtomicReference<>();
m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
m_configNeedForceRefresh = new AtomicBoolean(true);
m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
m_configUtil.getOnErrorRetryInterval() * 8);
gson = new Gson();
// 尝试同步
this.trySync();
this.schedulePeriodicRefresh();
this.scheduleLongPollingRefresh();
}

然后是同步配置,下面的日志异常经常可以看到,比如配置拉取地址不通

1
2
3
4
5
6
7
8
9
10
11
12
13
com.ctrip.framework.apollo.internals.AbstractConfigRepository#trySync
protected boolean trySync() {
try {
sync();
return true;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
logger
.warn("Sync config failed, will retry. Repository {}, reason: {}", this.getClass(), ExceptionUtil
.getDetailMessage(ex));
}
return false;
}

实际的同步方法,加了synchronized锁,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
com.ctrip.framework.apollo.internals.RemoteConfigRepository#sync
@Override
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");

try {
// 获取本地配置
ApolloConfig previous = m_configCache.get();
// 获取配置
ApolloConfig current = loadApolloConfig();

//reference equals means HTTP 304
if (previous != current) {
logger.debug("Remote Config refreshed!");
m_configCache.set(current);
this.fireRepositoryChange(m_namespace, this.getConfig());
}

if (current != null) {
Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
current.getReleaseKey());
}

transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}

然后走到这

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
com.ctrip.framework.apollo.internals.RemoteConfigRepository#loadApolloConfig
private ApolloConfig loadApolloConfig() {
if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
//wait at most 5 seconds
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
}
String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster();
String dataCenter = m_configUtil.getDataCenter();
Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, m_namespace));
int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1;
long onErrorSleepTime = 0; // 0 means no sleep
Throwable exception = null;

// 获取配置
List<ServiceDTO> configServices = getConfigServices();
String url = null;
for (int i = 0; i < maxRetries; i++) {
List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
Collections.shuffle(randomConfigServices);
//Access the server which notifies the client first
if (m_longPollServiceDto.get() != null) {
randomConfigServices.add(0, m_longPollServiceDto.getAndSet(null));
}

for (ServiceDTO configService : randomConfigServices) {
if (onErrorSleepTime > 0) {
logger.warn(
"Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}",
onErrorSleepTime, m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, m_namespace);

try {
m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime);
} catch (InterruptedException e) {
//ignore
}
}

url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
dataCenter, m_remoteMessages.get(), m_configCache.get());

logger.debug("Loading config from {}", url);
HttpRequest request = new HttpRequest(url);

Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig");
transaction.addData("Url", url);
try {

HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class);
m_configNeedForceRefresh.set(false);
m_loadConfigFailSchedulePolicy.success();

transaction.addData("StatusCode", response.getStatusCode());
transaction.setStatus(Transaction.SUCCESS);

if (response.getStatusCode() == 304) {
logger.debug("Config server responds with 304 HTTP status code.");
return m_configCache.get();
}

ApolloConfig result = response.getBody();

logger.debug("Loaded config for {}: {}", m_namespace, result);

return result;
} catch (ApolloConfigStatusCodeException ex) {
ApolloConfigStatusCodeException statusCodeException = ex;
//config not found
if (ex.getStatusCode() == 404) {
String message = String.format(
"Could not find config for namespace - appId: %s, cluster: %s, namespace: %s, " +
"please check whether the configs are released in Apollo!",
appId, cluster, m_namespace);
statusCodeException = new ApolloConfigStatusCodeException(ex.getStatusCode(),
message);
}
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException));
transaction.setStatus(statusCodeException);
exception = statusCodeException;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
exception = ex;
} finally {
transaction.complete();
}

// if force refresh, do normal sleep, if normal config load, do exponential sleep
onErrorSleepTime = m_configNeedForceRefresh.get() ? m_configUtil.getOnErrorRetryInterval() :
m_loadConfigFailSchedulePolicy.fail();
}

}
String message = String.format(
"Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s",
appId, cluster, m_namespace, url);
throw new ApolloConfigException(message, exception);
}
1
2
3
4
5
6
7
8
9
com.ctrip.framework.apollo.internals.RemoteConfigRepository#getConfigServices
private List<ServiceDTO> getConfigServices() {
List<ServiceDTO> services = m_serviceLocator.getConfigServices();
if (services.size() == 0) {
throw new ApolloConfigException("No available config service");
}

return services;
}
1
2
3
4
5
6
7
8
com.ctrip.framework.apollo.internals.ConfigServiceLocator#getConfigServices
public List<ServiceDTO> getConfigServices() {
if (m_configServices.get().isEmpty()) {
updateConfigServices();
}

return m_configServices.get();
}

更新配置服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
com.ctrip.framework.apollo.internals.ConfigServiceLocator#updateConfigServices
private synchronized void updateConfigServices() {
String url = assembleMetaServiceUrl();

HttpRequest request = new HttpRequest(url);
int maxRetries = 2;
Throwable exception = null;

for (int i = 0; i < maxRetries; i++) {
Transaction transaction = Tracer.newTransaction("Apollo.MetaService", "getConfigService");
transaction.addData("Url", url);
try {
// 发起 http 请求获取配置
HttpResponse<List<ServiceDTO>> response = m_httpUtil.doGet(request, m_responseType);
transaction.setStatus(Transaction.SUCCESS);
List<ServiceDTO> services = response.getBody();
if (services == null || services.isEmpty()) {
logConfigService("Empty response!");
continue;
}
setConfigServices(services);
return;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
exception = ex;
} finally {
transaction.complete();
}

try {
m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(m_configUtil.getOnErrorRetryInterval());
} catch (InterruptedException ex) {
//ignore
}
}

throw new ApolloConfigException(
String.format("Get config services failed from %s", url), exception);
}

题目介绍

Given an integer array nums, return the sum of floor(nums[i] / nums[j]) for all pairs of indices 0 <= i, j < nums.length in the array. Since the answer may be too large, return it modulo 10^9 + 7.

The floor() function returns the integer part of the division.

对应中文
给你一个整数数组 nums ,请你返回所有下标对 0 <= i, j < nums.length 的 floor(nums[i] / nums[j]) 结果之和。由于答案可能会很大,请你返回答案对10^9 + 7 取余 的结果。

函数 floor() 返回输入数字的整数部分。

示例

Example 1:

Input: nums = [2,5,9]
Output: 10
Explanation:
floor(2 / 5) = floor(2 / 9) = floor(5 / 9) = 0
floor(2 / 2) = floor(5 / 5) = floor(9 / 9) = 1
floor(5 / 2) = 2
floor(9 / 2) = 4
floor(9 / 5) = 1
We calculate the floor of the division for every pair of indices in the array then sum them up.

Example 2:

Input: nums = [7,7,7,7,7,7,7]
Output: 49

Constraints:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

简析

这题不愧是 hard,要不是看了讨论区的一个大神的解答感觉从头做得想好久,
主要是两点,对于任何一个在里面的数,随便举个例子是 k,最简单的就是循环所有数对 k 除一下,
这样效率会很低,那么对于 k 有什么规律呢,就是对于所有小于 k 的数,往下取整都是 0,所以不用考虑,
对于所有大于 k 的数我们可以分成一个个的区间,[k,2k-1),[2k,3k-1),[3k,4k-1)……对于这些区间的
除了 k 往下取整,每个区间内的都是一样的,所以可以简化为对于任意一个 k,我只要知道与k 相同的有多少个,然后比 k 大的各个区间各有多少个数就可以了

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static final int MAXE5 = 100_000;

static final int MODULUSE9 = 1_000_000_000 + 7;

public int sumOfFlooredPairs(int[] nums) {
int[] counts = new int[MAXE5+1];
for (int num : nums) {
counts[num]++;
}
// 这里就是很巧妙的给后一个加上前一个的值,这样其实前后任意两者之差就是这中间的元素数量
for (int i = 1; i <= MAXE5; i++) {
counts[i] += counts[i - 1];
}
long total = 0;
for (int i = 1; i <= MAXE5; i++) {
long sum = 0;
if (counts[i] == counts[i-1]) {
continue;
}
for (int j = 1; i*j <= MAXE5; j++) {
int min = i * j - 1;
int upper = i * (j + 1) - 1;
// 在每一个区间内的数量,
sum += (counts[Math.min(upper, MAXE5)] - counts[min]) * (long)j;
}
// 左边乘数的数量,即 i 位置的元素数量
total = (total + (sum % MODULUSE9 ) * (counts[i] - counts[i-1])) % MODULUSE9;
}
return (int)total;
}

贴出来大神的解析,解析

结果

在用 Apollo 作为配置中心的过程中才到过几个坑,这边记录下,因为运行 java 服务的启动参数一般比较固定,所以我们在一个新环境里运行的时候没有特意去检查,然后突然发现业务上有一些数据异常,排查之后才发现java 服务连接了测试环境的 apollo,而原因是因为环境变量传了-Denv=fat,而在我们的环境配置中 fat 就是代表测试环境, 其实应该是-Denv=pro,而 apollo 总共有这些环境

1
2
3
4
5
6
7
8
9
public enum Env{
LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN;

public static Env fromString(String env) {
Env environment = EnvUtils.transformEnv(env);
Preconditions.checkArgument(environment != UNKNOWN, String.format("Env %s is invalid", env));
return environment;
}
}

而这些解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Here is the brief description for all the predefined environments:
* <ul>
* <li>LOCAL: Local Development environment, assume you are working at the beach with no network access</li>
* <li>DEV: Development environment</li>
* <li>FWS: Feature Web Service Test environment</li>
* <li>FAT: Feature Acceptance Test environment</li>
* <li>UAT: User Acceptance Test environment</li>
* <li>LPT: Load and Performance Test environment</li>
* <li>PRO: Production environment</li>
* <li>TOOLS: Tooling environment, a special area in production environment which allows
* access to test environment, e.g. Apollo Portal should be deployed in tools environment</li>
* </ul>
*/

那如果要在运行时知道 apollo 当前使用的环境可以用这个

1
Env apolloEnv = ApolloInjector.getInstance(ConfigUtil.class).getApolloEnv();

简单记录下。

题目介绍

You start at the cell (rStart, cStart) of an rows x cols grid facing east. The northwest corner is at the first row and column in the grid, and the southeast corner is at the last row and column.

You will walk in a clockwise spiral shape to visit every position in this grid. Whenever you move outside the grid’s boundary, we continue our walk outside the grid (but may return to the grid boundary later.). Eventually, we reach all rows * cols spaces of the grid.

Return an array of coordinates representing the positions of the grid in the order you visited them.

Example 1:

Input: rows = 1, cols = 4, rStart = 0, cStart = 0
Output: [[0,0],[0,1],[0,2],[0,3]]

Example 2:

Input: rows = 5, cols = 6, rStart = 1, cStart = 4
Output: [[1,4],[1,5],[2,5],[2,4],[2,3],[1,3],[0,3],[0,4],[0,5],[3,5],[3,4],[3,3],[3,2],[2,2],[1,2],[0,2],[4,5],[4,4],[4,3],[4,2],[4,1],[3,1],[2,1],[1,1],[0,1],[4,0],[3,0],[2,0],[1,0],[0,0]]

Constraints:

  • 1 <= rows, cols <= 100
  • 0 <= rStart < rows
  • 0 <= cStart < cols

简析

这个题主要是要相同螺旋矩阵的转变方向的边界判断,已经相同步长会行进两次这个规律,写代码倒不复杂

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public int[][] spiralMatrixIII(int rows, int cols, int rStart, int cStart) {
int size = rows * cols;
int x = rStart, y = cStart;
// 返回的二维矩阵
int[][] matrix = new int[size][2];
// 传入的参数就是入口第一个
matrix[0][0] = rStart;
matrix[0][1] = cStart;
// 作为数量
int z = 1;
// 步进,1,1,2,2,3,3,4 ... 螺旋矩阵的增长
int a = 1;
// 方向 1 表示右,2 表示下,3 表示左,4 表示上
int dir = 1;
while (z < size) {
for (int i = 0; i < 2; i++) {
for (int j= 0; j < a; j++) {
// 处理方向
if (dir % 4 == 1) {
y++;
} else if (dir % 4 == 2) {
x++;
} else if (dir % 4 == 3) {
y--;
} else {
x--;
}
// 如果在实际矩阵内
if (x < rows && y < cols && x >= 0 && y >= 0) {
matrix[z][0] = x;
matrix[z][1] = y;
z++;
}
}
// 转变方向
dir++;
}
// 步进++
a++;
}
return matrix;
}

结果

0%